/* -*- mode: jde; c-basic-offset: 2; indent-tabs-mode: nil -*- */ /* Part of the Arduino project - http://arduino.berlios.de Based on the Processing project - http://www.processing.org Copyright (c) 2004-05 Ben Fry and Casey Reas Copyright (c) 2001-04 Massachusetts Institute of Technology This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package processing.app; import processing.app.syntax.*; import java.awt.*; import java.awt.event.*; import java.io.*; import java.net.*; import java.util.*; import java.util.zip.*; import javax.swing.*; import javax.swing.border.*; import javax.swing.event.*; import javax.swing.filechooser.*; import javax.swing.text.*; import javax.swing.undo.*; import processing.core.PApplet; // TODO change this to use the Java Preferences API // http://www.onjava.com/pub/a/onjava/synd/2001/10/17/j2se.html // http://www.particle.kth.se/~lindsey/JavaCourse/Book/Part1/Java/Chapter10/Preferences.html /** * Storage class for user preferences and environment settings. *

* This class no longer uses the Properties class, since * properties files are iso8859-1, which is highly likely to * be a problem when trying to save sketch folders and locations. *

* This is very poorly put together, that the prefs panel and the * actual prefs i/o is part of the same code. But there hasn't yet * been a compelling reason to bother with the separation. */ public class Preferences { // what to call the feller static final String PREFS_FILE = "preferences.txt"; // platform strings (used to get settings for specific platforms) static final String platforms[] = { "other", "windows", "macos9", "macosx", "linux" }; // prompt text stuff static final String PROMPT_YES = "Yes"; static final String PROMPT_NO = "No"; static final String PROMPT_CANCEL = "Cancel"; static final String PROMPT_OK = "OK"; static final String PROMPT_SEND = "Send"; static final String PROMPT_BROWSE = "Browse"; /** * Standardized width for buttons. Mac OS X 10.3 wants 70 as its default, * Windows XP needs 66, and Linux needs 76, so 76 seems proper. */ static public int BUTTON_WIDTH = 76; /** * Standardized button height. Mac OS X 10.3 (Java 1.4) wants 29, * presumably because it now includes the blue border, where it didn't * in Java 1.3. Windows XP only wants 23 (not sure what default Linux * would be). Because of the disparity, on Mac OS X, it will be set * inside a static block. */ static public int BUTTON_HEIGHT = 24; /* // remove this for 0121, because quaqua takes care of it static { if (Base.isMacOS()) BUTTON_HEIGHT = 29; } */ // value for the size bars, buttons, etc static final int GRID_SIZE = 33; // indents and spacing standards. these probably need to be modified // per platform as well, since macosx is so huge, windows is smaller, // and linux is all over the map static final int GUI_BIG = 13; static final int GUI_BETWEEN = 10; static final int GUI_SMALL = 6; // gui elements //JDialog dialog; JFrame dialog; int wide, high; JTextField sketchbookLocationField; JCheckBox exportSeparateBox; JCheckBox sketchPromptBox; JCheckBox sketchCleanBox; JCheckBox externalEditorBox; JCheckBox memoryOverrideBox; JTextField memoryField; JCheckBox checkUpdatesBox; JTextField fontSizeField; // the calling editor, so updates can be applied Editor editor; // data model // we have multiple preference files, one main one and a few subsidiary // ones with prefixes. the preferences from the main file go in table // and are saved back to the main file. the preferences from the // subsidiary files are stored in prefixes (which maps a prefix string to // a Hashtable mapping unprefixed keys to values) and are not saved. static Hashtable table = new Hashtable(); static Hashtable prefixes = new Hashtable(); static File preferencesFile; static public void init() { // start by loading the defaults, in case something // important was deleted from the user prefs try { load(Base.getStream("preferences.txt")); } catch (Exception e) { Base.showError(null, "Could not read default settings.\n" + "You'll need to reinstall Arduino.", e); } // check for platform-specific properties in the defaults String platformExtension = "." + platforms[processing.core.PApplet.platform]; int extensionLength = platformExtension.length(); Enumeration e = table.keys(); //properties.propertyNames(); while (e.hasMoreElements()) { String key = (String) e.nextElement(); if (key.endsWith(platformExtension)) { // this is a key specific to a particular platform String actualKey = key.substring(0, key.length() - extensionLength); String value = get(key); table.put(actualKey, value); } } // other things that have to be set explicitly for the defaults setColor("run.window.bgcolor", SystemColor.control); // next load user preferences file preferencesFile = Base.getSettingsFile(PREFS_FILE); if (!preferencesFile.exists()) { // create a new preferences file if none exists // saves the defaults out to the file save(); } else { // load the previous preferences file try { load(new FileInputStream(preferencesFile)); } catch (Exception ex) { Base.showError("Error reading preferences", "Error reading the preferences file. " + "Please delete (or move)\n" + preferencesFile.getAbsolutePath() + " and restart Arduino.", ex); } } try { load(new FileInputStream(new File( System.getProperty("user.dir") + File.separator + "hardware" + File.separator + "boards.txt")), "boards"); } catch (Exception ex) { Base.showError("Error reading board definitions", "Error reading the board definitions file. " + "Please re-download or re-unzip Arduino.\n", ex); } try { load(new FileInputStream(new File( System.getProperty("user.dir") + File.separator + "hardware" + File.separator + "programmers.txt")), "programmers"); } catch (Exception ex) { Base.showError("Error reading programmers definitions", "Error reading the programmers definitions file. " + "Please re-download or re-unzip Arduino.\n", ex); } } public Preferences() { // setup dialog for the prefs //dialog = new JDialog(editor, "Preferences", true); dialog = new JFrame("Preferences"); dialog.setResizable(false); Container pain = dialog.getContentPane(); pain.setLayout(null); int top = GUI_BIG; int left = GUI_BIG; int right = 0; JLabel label; JButton button, button2; JComboBox combo; Dimension d, d2, d3; int h, v, vmax; // [ ] Prompt for name and folder when creating new sketch sketchPromptBox = new JCheckBox("Prompt for name when opening or creating a sketch"); pain.add(sketchPromptBox); d = sketchPromptBox.getPreferredSize(); sketchPromptBox.setBounds(left, top, d.width, d.height); right = Math.max(right, left + d.width); top += d.height + GUI_BETWEEN; // [ ] Delete empty sketches on Quit sketchCleanBox = new JCheckBox("Delete empty sketches on Quit"); pain.add(sketchCleanBox); d = sketchCleanBox.getPreferredSize(); sketchCleanBox.setBounds(left, top, d.width, d.height); right = Math.max(right, left + d.width); top += d.height + GUI_BETWEEN; // Sketchbook location: // [...............................] [ Browse ] label = new JLabel("Sketchbook location:"); pain.add(label); d = label.getPreferredSize(); label.setBounds(left, top, d.width, d.height); top += d.height; // + GUI_SMALL; sketchbookLocationField = new JTextField(40); pain.add(sketchbookLocationField); d = sketchbookLocationField.getPreferredSize(); button = new JButton(PROMPT_BROWSE); button.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { /* JFileChooser fc = new JFileChooser(); fc.setSelectedFile(new File(sketchbookLocationField.getText())); fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); int returned = fc.showOpenDialog(new JDialog()); if (returned == JFileChooser.APPROVE_OPTION) { File file = fc.getSelectedFile(); sketchbookLocationField.setText(file.getAbsolutePath()); } */ File dflt = new File(sketchbookLocationField.getText()); File file = Base.selectFolder("Select new sketchbook location", dflt, dialog); if (file != null) { sketchbookLocationField.setText(file.getAbsolutePath()); } } }); pain.add(button); d2 = button.getPreferredSize(); // take max height of all components to vertically align em vmax = Math.max(d.height, d2.height); //label.setBounds(left, top + (vmax-d.height)/2, // d.width, d.height); //h = left + d.width + GUI_BETWEEN; sketchbookLocationField.setBounds(left, top + (vmax-d.height)/2, d.width, d.height); h = left + d.width + GUI_SMALL; //GUI_BETWEEN; button.setBounds(h, top + (vmax-d2.height)/2, d2.width, d2.height); right = Math.max(right, h + d2.width + GUI_BIG); top += vmax + GUI_BETWEEN; // Editor font size [ ] Container box = Box.createHorizontalBox(); label = new JLabel("Editor font size: "); box.add(label); fontSizeField = new JTextField(4); box.add(fontSizeField); label = new JLabel(" (requires restart of Arduino)"); box.add(label); pain.add(box); d = box.getPreferredSize(); box.setBounds(left, top, d.width, d.height); Font editorFont = Preferences.getFont("editor.font"); fontSizeField.setText(String.valueOf(editorFont.getSize())); top += d.height + GUI_BETWEEN; // [ ] Use external editor externalEditorBox = new JCheckBox("Use external editor"); pain.add(externalEditorBox); d = externalEditorBox.getPreferredSize(); externalEditorBox.setBounds(left, top, d.width, d.height); right = Math.max(right, left + d.width); top += d.height + GUI_BETWEEN; // [ ] Check for updates on startup checkUpdatesBox = new JCheckBox("Check for updates on startup"); pain.add(checkUpdatesBox); d = checkUpdatesBox.getPreferredSize(); checkUpdatesBox.setBounds(left, top, d.width, d.height); right = Math.max(right, left + d.width); top += d.height + GUI_BETWEEN; // More preferences are in the ... label = new JLabel("More preferences can be edited directly in the file"); pain.add(label); d = label.getPreferredSize(); label.setForeground(Color.gray); label.setBounds(left, top, d.width, d.height); right = Math.max(right, left + d.width); top += d.height; // + GUI_SMALL; label = new JLabel(preferencesFile.getAbsolutePath()); pain.add(label); d = label.getPreferredSize(); label.setBounds(left, top, d.width, d.height); right = Math.max(right, left + d.width); top += d.height; label = new JLabel("(edit only when Arduino is not running)"); pain.add(label); d = label.getPreferredSize(); label.setForeground(Color.gray); label.setBounds(left, top, d.width, d.height); right = Math.max(right, left + d.width); top += d.height; // + GUI_SMALL; // [ OK ] [ Cancel ] maybe these should be next to the message? button = new JButton(PROMPT_OK); button.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { applyFrame(); disposeFrame(); } }); pain.add(button); d2 = button.getPreferredSize(); BUTTON_HEIGHT = d2.height; h = right - (BUTTON_WIDTH + GUI_SMALL + BUTTON_WIDTH); button.setBounds(h, top, BUTTON_WIDTH, BUTTON_HEIGHT); h += BUTTON_WIDTH + GUI_SMALL; button = new JButton(PROMPT_CANCEL); button.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { disposeFrame(); } }); pain.add(button); button.setBounds(h, top, BUTTON_WIDTH, BUTTON_HEIGHT); top += BUTTON_HEIGHT + GUI_BETWEEN; // finish up wide = right + GUI_BIG; high = top + GUI_SMALL; //setSize(wide, high); // closing the window is same as hitting cancel button dialog.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { disposeFrame(); } }); ActionListener disposer = new ActionListener() { public void actionPerformed(ActionEvent actionEvent) { disposeFrame(); } }; Base.registerWindowCloseKeys(dialog.getRootPane(), disposer); Dimension screen = Toolkit.getDefaultToolkit().getScreenSize(); dialog.setLocation((screen.width - wide) / 2, (screen.height - high) / 2); dialog.pack(); // get insets Insets insets = dialog.getInsets(); dialog.setSize(wide + insets.left + insets.right, high + insets.top + insets.bottom); // handle window closing commands for ctrl/cmd-W or hitting ESC. pain.addKeyListener(new KeyAdapter() { public void keyPressed(KeyEvent e) { KeyStroke wc = Editor.WINDOW_CLOSE_KEYSTROKE; if ((e.getKeyCode() == KeyEvent.VK_ESCAPE) || (KeyStroke.getKeyStrokeForEvent(e).equals(wc))) { disposeFrame(); } } }); } /* protected JRootPane createRootPane() { System.out.println("creating root pane esc received"); ActionListener actionListener = new ActionListener() { public void actionPerformed(ActionEvent actionEvent) { //setVisible(false); System.out.println("esc received"); } }; JRootPane rootPane = new JRootPane(); KeyStroke stroke = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0); rootPane.registerKeyboardAction(actionListener, stroke, JComponent.WHEN_IN_FOCUSED_WINDOW); return rootPane; } */ public Dimension getPreferredSize() { return new Dimension(wide, high); } // ................................................................. /** * Close the window after an OK or Cancel. */ public void disposeFrame() { dialog.dispose(); } /** * Change internal settings based on what was chosen in the prefs, * then send a message to the editor saying that it's time to do the same. */ public void applyFrame() { // put each of the settings into the table setBoolean("sketchbook.prompt", sketchPromptBox.isSelected()); setBoolean("sketchbook.auto_clean", sketchCleanBox.isSelected()); set("sketchbook.path", sketchbookLocationField.getText()); setBoolean("editor.external", externalEditorBox.isSelected()); setBoolean("update.check", checkUpdatesBox.isSelected()); String newSizeText = fontSizeField.getText(); try { int newSize = Integer.parseInt(newSizeText.trim()); String pieces[] = PApplet.split(get("editor.font"), ','); pieces[2] = String.valueOf(newSize); set("editor.font", PApplet.join(pieces, ',')); } catch (Exception e) { System.err.println("ignoring invalid font size " + newSizeText); } editor.applyPreferences(); } public void showFrame(Editor editor) { this.editor = editor; // set all settings entry boxes to their actual status sketchPromptBox.setSelected(getBoolean("sketchbook.prompt")); sketchCleanBox.setSelected(getBoolean("sketchbook.auto_clean")); sketchbookLocationField.setText(get("sketchbook.path")); externalEditorBox.setSelected(getBoolean("editor.external")); checkUpdatesBox.setSelected(getBoolean("update.check")); dialog.show(); } // ................................................................. static public void load(InputStream input) throws IOException { load(input, null); } static public void load(InputStream input, String prefix) throws IOException { BufferedReader reader = new BufferedReader(new InputStreamReader(input)); Hashtable table = Preferences.table; if (prefix != null) { table = new Hashtable(); prefixes.put(prefix, table); } //table = new Hashtable(); String line = null; while ((line = reader.readLine()) != null) { if ((line.length() == 0) || (line.charAt(0) == '#')) continue; // this won't properly handle = signs being in the text int equals = line.indexOf('='); if (equals != -1) { String key = line.substring(0, equals).trim(); String value = line.substring(equals + 1).trim(); table.put(key, value); } } reader.close(); } // ................................................................. static public void save() { try { FileOutputStream output = new FileOutputStream(preferencesFile); PrintWriter writer = new PrintWriter(new OutputStreamWriter(output)); Enumeration e = table.keys(); //properties.propertyNames(); while (e.hasMoreElements()) { String key = (String) e.nextElement(); writer.println(key + "=" + ((String) table.get(key))); } writer.flush(); writer.close(); /* FileOutputStream output = null; if ((Base.platform == Base.MACOSX) || (Base.platform == Base.MACOS9)) { output = new FileOutputStream("lib/preferences.txt"); } else { // win95/98/ME doesn't set cwd properly URL url = getClass().getResource("buttons.gif"); String urlstr = url.getFile(); urlstr = urlstr.substring(0, urlstr.lastIndexOf("/") + 1) + ".properties"; output = new FileOutputStream(URLDecoder.decode(urlstr)); } */ /* //base.storePreferences(); Properties skprops = new Properties(); //Rectangle window = Base.frame.getBounds(); Rectangle window = editor.getBounds(); Dimension screen = Toolkit.getDefaultToolkit().getScreenSize(); skprops.put("last.window.x", String.valueOf(window.x)); skprops.put("last.window.y", String.valueOf(window.y)); skprops.put("last.window.w", String.valueOf(window.width)); skprops.put("last.window.h", String.valueOf(window.height)); skprops.put("last.screen.w", String.valueOf(screen.width)); skprops.put("last.screen.h", String.valueOf(screen.height)); skprops.put("last.sketch.name", sketchName); skprops.put("last.sketch.directory", sketchDir.getAbsolutePath()); //skprops.put("user.name", userName); skprops.put("last.divider.location", String.valueOf(splitPane.getDividerLocation())); // skprops.put("editor.external", externalEditor ? "true" : "false"); //skprops.put("serial.port", Preferences.get("serial.port", "unspecified")); // save() is deprecated, and didn't properly // throw exceptions when it wasn't working skprops.store(output, "Settings for arduino. " + "See lib/preferences.txt for defaults."); // need to close the stream.. didn't do this before skprops.close(); */ } catch (IOException ex) { Base.showWarning(null, "Error while saving the settings file", ex); //e.printStackTrace(); } } // ................................................................. // all the information from preferences.txt //static public String get(String attribute) { //return get(attribute, null); //} static public String get(String attribute /*, String defaultValue */) { // if the attribute starts with a prefix used by one of our subsidiary // preference files, look up the attribute in that file's Hashtable // (don't override with or fallback to the main file). otherwise, // look up the attribute in the main file's Hashtable. Hashtable table = Preferences.table; if (attribute.indexOf('.') != -1) { String prefix = attribute.substring(0, attribute.indexOf('.')); if (prefixes.containsKey(prefix)) { table = (Hashtable) prefixes.get(prefix); attribute = attribute.substring(attribute.indexOf('.') + 1); } } return (String) table.get(attribute); /* //String value = (properties != null) ? //properties.getProperty(attribute) : applet.getParameter(attribute); String value = properties.getProperty(attribute); return (value == null) ? defaultValue : value; */ } /** * Get the top-level key prefixes defined in the subsidiary file loaded with * the given prefix. For example, if the file contains: * foo.count=1 * bar.count=2 * baz.count=3 * this will return { "foo", "bar", "baz" }. */ static public Iterator getSubKeys(String prefix) { if (!prefixes.containsKey(prefix)) return null; Set subkeys = new HashSet(); for (Enumeration e = ((Hashtable) prefixes.get(prefix)).keys(); e.hasMoreElements(); ) { String subkey = (String) e.nextElement(); if (subkey.indexOf('.') != -1) subkey = subkey.substring(0, subkey.indexOf('.')); subkeys.add(subkey); } return subkeys.iterator(); } static public void set(String attribute, String value) { //preferences.put(attribute, value); table.put(attribute, value); } static public boolean getBoolean(String attribute) { String value = get(attribute); //, null); return (new Boolean(value)).booleanValue(); /* supposedly not needed, because anything besides 'true' (ignoring case) will just be false.. so if malformed -> false if (value == null) return defaultValue; try { return (new Boolean(value)).booleanValue(); } catch (NumberFormatException e) { System.err.println("expecting an integer: " + attribute + " = " + value); } return defaultValue; */ } static public void setBoolean(String attribute, boolean value) { set(attribute, value ? "true" : "false"); } static public int getInteger(String attribute /*, int defaultValue*/) { return Integer.parseInt(get(attribute)); /* String value = get(attribute, null); if (value == null) return defaultValue; try { return Integer.parseInt(value); } catch (NumberFormatException e) { // ignored will just fall through to returning the default System.err.println("expecting an integer: " + attribute + " = " + value); } return defaultValue; //if (value == null) return defaultValue; //return (value == null) ? defaultValue : //Integer.parseInt(value); */ } static public void setInteger(String key, int value) { set(key, String.valueOf(value)); } static public Color getColor(String name /*, Color otherwise*/) { Color parsed = null; String s = get(name); //, null); //System.out.println(name + " = " + s); if ((s != null) && (s.indexOf("#") == 0)) { try { int v = Integer.parseInt(s.substring(1), 16); parsed = new Color(v); } catch (Exception e) { } } //if (parsed == null) return otherwise; return parsed; } static public void setColor(String attr, Color what) { String r = Integer.toHexString(what.getRed()); String g = Integer.toHexString(what.getGreen()); String b = Integer.toHexString(what.getBlue()); set(attr, "#" + r.substring(r.length() - 2) + g.substring(g.length() - 2) + b.substring(b.length() - 2)); } static public Font getFont(String which /*, Font otherwise*/) { //System.out.println("getting font '" + which + "'"); String str = get(which); //if (str == null) return otherwise; // ENABLE LATER StringTokenizer st = new StringTokenizer(str, ","); String fontname = st.nextToken(); String fontstyle = st.nextToken(); return new Font(fontname, ((fontstyle.indexOf("bold") != -1) ? Font.BOLD : 0) | ((fontstyle.indexOf("italic") != -1) ? Font.ITALIC : 0), Integer.parseInt(st.nextToken())); } static public SyntaxStyle getStyle(String what /*, String dflt*/) { String str = get("editor." + what + ".style"); //, dflt); StringTokenizer st = new StringTokenizer(str, ","); String s = st.nextToken(); if (s.indexOf("#") == 0) s = s.substring(1); Color color = new Color(Integer.parseInt(s, 16)); s = st.nextToken(); boolean bold = (s.indexOf("bold") != -1); boolean italic = (s.indexOf("italic") != -1); //System.out.println(what + " = " + str + " " + bold + " " + italic); return new SyntaxStyle(color, italic, bold); } } // Default serial port: [ COM1 + ] /* label = new JLabel("Default serial port:"); pain.add(label); d = label.getPreferredSize(); Vector list = buildPortList(); combo = new JComboBox(list); pain.add(combo); d2 = combo.getPreferredSize(); if (list.size() == 0) { label.setEnabled(false); combo.setEnabled(false); } else { String defaultName = Preferences.get("serial.port", "unspecified"); combo.setSelectedItem(defaultName); } vmax = Math.max(d.height, d2.height); label.setBounds(left, top + (vmax-d.height)/2, d.width, d.height); h = left + d.width + BETWEEN; combo.setBounds(h, top + (vmax-d2.height)/2, d2.width, d2.height); right = Math.max(right, h + d2.width + BIG); top += vmax + BETWEEN; */ // open the last-used sketch, etc //public void init() { //String what = path + File.separator + name + ".pde"; ///String serialPort = skprops.getProperty("serial.port"); //if (serialPort != null) { // properties.put("serial.port", serialPort); //} //boolean ee = new Boolean(skprops.getProperty("editor.external", "false")).booleanValue(); //editor.setExternalEditor(ee); ///} catch (Exception e) { // this exception doesn't matter, it's just the normal course of things // the app reaches here when no sketch.properties file exists //e.printStackTrace(); // indicator that this is the first time this feller has used p5 //firstTime = true; // even if folder for 'default' user doesn't exist, or // sketchbook itself is missing, mkdirs() will make it happy //userName = "default"; // doesn't exist, not available, make my own //skNew(); //} //}