/* * PrefsUtil.java * (FScape) * * Copyright (c) 2001-2016 Hanns Holger Rutz. All rights reserved. * * This software is published under the GNU General Public License v3+ * * * For further information, please contact Hanns Holger Rutz at * contact@sciss.de */ package de.sciss.fscape.util; import de.sciss.fscape.Application; import de.sciss.fscape.gui.PathField; import de.sciss.io.IOUtil; import org.w3c.dom.DOMException; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Map; import java.util.NoSuchElementException; import java.util.StringTokenizer; import java.util.prefs.BackingStoreException; import java.util.prefs.Preferences; /** * A helper class for programme preferences. It * contains the globally known preferences keys, * adds support for default preferences generation * and some geometric object (de)serialization. * Has utility methods for clearing preferences trees * and importing and exporting from/to XML. */ public class PrefsUtil { // ------------ root node level ------------ /** * Value: Double representing the application * version of Meloncillo last time prefs was saved.<br> * Has default value: no!<br> * Node: root */ public static final String KEY_VERSION = "version"; // double : app version // /** // * Value: String representing the pathname // * of the temporary files directory.<br> // * Has default value: yes!<br> // * Node: root // */ // public static final String KEY_TEMPDIR = "tmpdir"; // string : pathname /** * Value: String representing the path list of * a user's set favourite directories. See PathList * and PathField documentation.<br> * Has default value: no!<br> * Node: root */ public static final String KEY_USERPATHS= "usrpaths"; // string : path list /** * Value: String representing a list of paths * of the recently used session files. See * PathList and MenuFactory.actionOpenRecentClass.<br> * Has default value: no!<br> * Node: root */ public static final String KEY_OPENRECENT= "recent"; // string: path list /** * Value: Boolean stating whether frame bounds * should be recalled a session file when it's * loaded. Has default value: yes!<br> * Node: root */ public static final String KEY_LOOKANDFEEL = "lookandfeel"; /** * Value: Boolean stating whether frame size (grow) boxes * intrude into the frame's pane. Has default value: yes!<br> * Node: root */ public static final String KEY_INTRUDINGSIZE = "intrudingsize"; // /** // * Value: String "(int) modifiers (int) keyCode" for online // * help accelerator. Has default value: yes!<br> // * Node: root // */ // public static final String KEY_KEYSTROKE_HELP = "keystrokehelp"; /** * Value: String representing a Point object * describing a windows location. Use stringToPoint.<br> * Has default value: no!<br> * Node: multiple occurences in shared -> (Frame-class) */ public static final String KEY_LOCATION = "location"; // point /** * Value: String representing a Dimension object * describing a windows size. Use stringToDimension.<br> * Has default value: no!<br> * Node: multiple occurences in shared -> (Frame-class) */ public static final String KEY_SIZE = "size"; // dimension /** * Value: Boolean stating whether a window is * shown or hidden.<br> * Has default value: no!<br> * Node: multiple occurrences in shared -> (Frame-class) */ public static final String KEY_VISIBLE = "visible"; // boolean public static final String KEY_BACKUP = "makebackups"; public static final String KEY_BAKDIR = "backupdir"; /** * Value: String of "native", "metal", "web", "submin" */ public static final String KEY_LAF_TYPE = "laf-type"; public static final String VALUE_LAF_TYPE_NATIVE = "native"; public static final String VALUE_LAF_TYPE_METAL = "metal"; public static final String VALUE_LAF_TYPE_SUBMIN_LIGHT = "light"; public static final String VALUE_LAF_TYPE_SUBMIN_DARK = "dark"; public static final String KEY_LAF_WINDOWS = "lafdecoration"; public static java.util.List createDefaults(Preferences mainPrefs, double lastVersion) { File f; // String value; // Preferences childPrefs, childPrefs2; // final String fs = File.separator; final boolean isMacOS = System.getProperty("os.name").contains("Mac OS"); // final boolean isWindows = System.getProperty( "os.name" ).indexOf( "Windows" ) >= 0; final java.util.List warnings = new ArrayList(); putDontOverwrite(IOUtil.getUserPrefs(), IOUtil.KEY_TEMPDIR, System.getProperty("java.io.tmpdir")); // // general // putDontOverwrite( GUIUtil.getUserPrefs(), HelpGlassPane.KEY_KEYSTROKE_HELP, strokeToPrefs( // KeyStroke.getKeyStroke( KeyEvent.VK_H, MenuFactory.MENU_SHORTCUT + KeyEvent.SHIFT_MASK ))); // putDontOverwrite( mainPrefs, KEY_LOOKANDFEEL, UIManager.getSystemLookAndFeelClassName() ); // putBooleanDontOverwrite( mainPrefs, KEY_INTRUDINGSIZE, isMacOS ); putBooleanDontOverwrite( mainPrefs, KEY_BACKUP, true ); if (mainPrefs.get(KEY_BAKDIR, null) == null) { f = new File(new File(System.getProperty("user.home"), Application.name), "bak"); if (!f.isDirectory()) { try { IOUtil.createEmptyDirectory( f ); } catch( IOException e1 ) { warnings.add( f.getAbsolutePath() + " : Could not create directory"); } } putDontOverwrite( mainPrefs, KEY_BAKDIR, f.getAbsolutePath() ); } // save current version mainPrefs.put(KEY_VERSION, Application.version); putDontOverwrite(mainPrefs, "audioFileRes" , PathField.getSoundResID(1)); putDontOverwrite(mainPrefs, "audioFileRate", PathField.getSoundRateID(2)); putDontOverwrite(mainPrefs, "headroom", new de.sciss.util.Param(-0.2, de.sciss.util.ParamSpace.spcAmpDecibels.unit).toString()); return warnings; } /* private static File findFile( String fileName, String[] folders ) { File f; for( int i = 0; i < folders.length; i++ ) { f = new File( folders[ i ], fileName ); if( f.exists() ) return f; } return null; } */ // --- custom put/get methods --- private static boolean putDontOverwrite(Preferences prefs, String key, String value) { boolean overwrite = prefs.get(key, null) == null; if (overwrite) { prefs.put(key, value); } return overwrite; } private static boolean putBooleanDontOverwrite(Preferences prefs, String key, boolean value) { boolean overwrite = prefs.get(key, null) == null; if (overwrite) { prefs.putBoolean(key, value); } return overwrite; } public static Rectangle stringToRectangle(String value) { Rectangle rect = null; StringTokenizer tok; if (value != null) { try { tok = new StringTokenizer(value); rect = new Rectangle(Integer.parseInt(tok.nextToken()), Integer.parseInt(tok.nextToken()), Integer.parseInt(tok.nextToken()), Integer.parseInt(tok.nextToken())); } catch (NoSuchElementException ignored) { } catch (NumberFormatException ignored) { } } return rect; } public static Point stringToPoint(String value) { Point pt = null; StringTokenizer tok; if (value != null) { try { tok = new StringTokenizer(value); pt = new Point(Integer.parseInt(tok.nextToken()), Integer.parseInt(tok.nextToken())); } catch (NoSuchElementException ignored) { } catch (NumberFormatException ignored) { } } return pt; } public static Dimension stringToDimension(String value) { Dimension dim = null; StringTokenizer tok; if (value != null) { try { tok = new StringTokenizer(value); dim = new Dimension(Integer.parseInt(tok.nextToken()), Integer.parseInt(tok.nextToken())); } catch (NoSuchElementException ignored) { } catch (NumberFormatException ignored) { } } return dim; } /** * Rectangle, z.B. von Frame.getBounds() in * einen String konvertieren, der als Prefs * gespeichert werden kann. Bei Fehler wird * null zurueckgeliefert. 'value' darf null sein. */ public static String rectangleToString(Rectangle value) { return (value != null ? (value.x + " " + value.y + " " + value.width + " " + value.height) : null); } public static String pointToString(Point value) { return (value != null ? (value.x + " " + value.y) : null); } public static String dimensionToString(Dimension value) { return (value != null ? (value.width + " " + value.height) : null); } /** * Converts a a key stroke's string representation as * from preference storage into a KeyStroke object. * * @param prefsValue a string representation of the form "modifiers keyCode" * or <code>null</code> * @return the KeyStroke parsed from the prefsValue or null if the string was * invalid or <code>null</code> */ public static KeyStroke prefsToStroke(String prefsValue) { if (prefsValue == null) return null; int i = prefsValue.indexOf(' '); KeyStroke prefsStroke = null; try { if (i < 0) return null; prefsStroke = KeyStroke.getKeyStroke( Integer.parseInt(prefsValue.substring(i + 1)), Integer.parseInt(prefsValue.substring(0, i))); } catch (NumberFormatException ignored) { } return prefsStroke; } /** * Converts a KeyStroke into a string representation for * preference storage. * * @param prefsStroke the KeyStroke to convert * @return a string representation of the form "modifiers keyCode" * or <code>null</code> if the prefsStroke is invalid or <code>null</code> */ public static String strokeToPrefs(KeyStroke prefsStroke) { if (prefsStroke == null) return null; else return String.valueOf(prefsStroke.getModifiers()) + ' ' + String.valueOf(prefsStroke.getKeyCode()); } /** * Traverse a preference node and optionally all * children nodes and remove any keys found. */ public static void removeAll( Preferences prefs, boolean deep ) throws BackingStoreException { String[] keys; String[] children; int i; keys = prefs.keys(); for (i = 0; i < keys.length; i++) { prefs.remove(keys[i]); } if (deep) { children = prefs.childrenNames(); for (i = 0; i < children.length; i++) { removeAll(prefs.node(children[i]), true); } } } /** * Get an Action object that will dump the * structure of the MultiTrackEditors of * all selected transmitters */ public static Action getDebugDumpAction() { AbstractAction a = new AbstractAction( "Dump preferences tree" ) { public void actionPerformed( ActionEvent e ) { debugDump( Application.userPrefs ); } private void debugDump( Preferences prefs ) { System.err.println( "------- debugDump prefs : "+prefs.name()+" -------" ); String[] keys; String[] children; String value; int i; try { keys = prefs.keys(); for( i = 0; i < keys.length; i++ ) { value = prefs.get( keys[i], null ); System.err.println( " key = '"+keys[i]+"' ; value = '"+value+"'" ); } children = prefs.childrenNames(); for( i = 0; i < children.length; i++ ) { debugDump( prefs.node( children[i] )); } } catch( BackingStoreException e1 ) { System.err.println( e1.getLocalizedMessage() ); } } }; return a; } /** * Similar to the XMLRepresentation interface, * this method will append an XML representation * of some preferences to an existing node. * * @param prefs the preferences node to write out. * @param deep - true to include a subtree with all * child preferences nodes. * @param domDoc the document in which the node will reside. * @param node the node to which a child is applied. */ public static void toXML( Preferences prefs, boolean deep, org.w3c.dom.Document domDoc, Element node, Map options ) throws IOException { String[] keys; String[] children; Element childElement, entry; String value; int i; //System.err.println( "node = "+prefs.name() ); try { keys = prefs.keys(); childElement = (Element) node.appendChild( domDoc.createElement( "map" )); for( i = 0; i < keys.length; i++ ) { value = prefs.get( keys[i], null ); //System.err.println( " key = "+keys[i]+"; value = "+value ); if( value == null ) continue; entry = (Element) childElement.appendChild( domDoc.createElement( "entry" )); entry.setAttribute( "key", keys[i] ); entry.setAttribute( "value", value ); } if (deep) { children = prefs.childrenNames(); for (i = 0; i < children.length; i++) { childElement = (Element) node.appendChild(domDoc.createElement("node")); childElement.setAttribute("name", children[i]); toXML(prefs.node(children[i]), true, domDoc, childElement, options); } } } catch (DOMException e1) { throw IOUtil.map(e1); } catch (BackingStoreException e2) { throw IOUtil.map(e2); } } /** * Similar to the XMLRepresentation interface, * this method will parse an XML representation * of some preferences and restore it's values. * * @param prefs the preferences node to import to. * @param domDoc the document in which the node resides. * @param rootElement the node whose children to parse. */ public static void fromXML( Preferences prefs, org.w3c.dom.Document domDoc, Element rootElement, Map options ) throws IOException { NodeList nl, nl2; Element childElement, entry; Node node; int i, j; try { nl = rootElement.getChildNodes(); for( i = 0; i < nl.getLength(); i++ ) { node = nl.item( i ); if( !(node instanceof Element) ) continue; childElement = (Element) node; nl2 = childElement.getElementsByTagName( "entry" ); for( j = 0; j < nl2.getLength(); j++ ) { entry = (Element) nl2.item( j ); prefs.put( entry.getAttribute( "key" ), entry.getAttribute( "value" )); //System.err.println( "auto : node = "+(prefs.name() )+"; key = "+entry.getAttribute( "key" )+"; value = "+entry.getAttribute( "value" ) ); } break; } for( ; i < nl.getLength(); i++ ) { node = nl.item( i ); if( !(node instanceof Element) ) continue; childElement = (Element) node; fromXML( prefs.node( childElement.getAttribute( "name" )), domDoc, childElement, options ); } } catch( DOMException e1 ) { throw IOUtil.map( e1 ); } } }