/*
* License: source-license.txt
* If this code is used independently, copy the license here.
*/
package wombat.util;
import java.awt.Color;
import java.awt.Component;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.io.*;
import java.net.URISyntaxException;
import java.util.*;
import java.util.prefs.*;
import javax.swing.*;
import org.xnap.commons.gui.shortcut.EmacsKeyBindings;
import wombat.gui.frames.*;
import wombat.gui.text.*;
import wombat.util.errors.ErrorManager;
import wombat.util.files.*;
/**
* Store options.
*
* TODO: Rewrite this using local files rather than Java preferences.
*/
public final class Options {
static Preferences prefs = Preferences.userRoot().node("wombat");
static JMenu optionsMenu;
// Used for the main display.
public static int DisplayWidth;
public static int DisplayHeight;
public static int DisplayTop;
public static int DisplayLeft;
public static boolean DisplayToolbar;
// Keyboard shortcuts.
public static String CommandRun;
public static String CommandFormat;
// Options menu options.
public static boolean LambdaMode;
public static boolean GreekMode;
public static boolean EmacsKeybindings;
public static boolean ConfirmOnRun;
public static boolean ConfirmOnClose;
public static boolean BackupOnSave;
public static boolean ViewLineNumbers;
// Syntax highlighting.
public static Map<String, Color> Colors;
public static Map<String, Integer> Keywords;
public static Map<String, String> KeywordHelpURLs;
public static Font Font;
public static int FontSize;
public static int FontWidth;
public static int FontHeight;
// Previous command history.
public static int SavedHistoryCount = 20;
public static String SavedHistory;
// Recently used documents.
public static String RecentDocuments;
// Greek mode characters
public static final String[][] GreekModeCharacters = new String[][]{
{"\u0391", "Alpha"},
{"\u0392", "Beta"},
{"\u0393", "Gamma"},
{"\u0394", "Delta"},
{"\u0395", "Epsilon"},
{"\u0396", "Zeta"},
{"\u0397", "Eta"},
{"\u0398", "Theta"},
{"\u0399", "Iota"},
{"\u039A", "Kappa"},
{"\u039B", "Lambda"},
{"\u039C", "Mu"},
{"\u039D", "Mu"},
{"\u039E", "Xi"},
{"\u039F", "Omicron"},
{"\u03A0", "Pi"},
{"\u03A1", "Rho"},
{"\u03A3", "Sigma"}, // skipping 03C2 which is final sigma
{"\u03A4", "Tau"},
{"\u03A5", "Upsilon"},
{"\u03A6", "Phi"},
{"\u03A7", "Chi"},
{"\u03A8", "Psi"},
{"\u03A9", "Omega"},
{"\u03B1", "alpha"},
{"\u03B2", "beta"},
{"\u03B3", "gamma"},
{"\u03B4", "delta"},
{"\u03B5", "epsilon"},
{"\u03B6", "zeta"},
{"\u03B7", "eta"},
{"\u03B8", "theta"},
{"\u03B9", "iota"},
{"\u03BA", "kappa"},
{"\u03BB", "lambda"},
{"\u03BC", "mu"},
{"\u03BD", "nu"},
{"\u03BE", "xi"},
{"\u03BF", "omicron"},
{"\u03C0", "pi"},
{"\u03C1", "rho"},
{"\u03C3", "sigma"}, // skipping 03C2 which is final sigma
{"\u03B4", "tau"},
{"\u03B5", "upsilon"},
{"\u03B6", "phi"},
{"\u03B7", "chi"},
{"\u03B8", "psi"},
{"\u03B9", "omega"}
};
/**
* Initialize.
*/
static { load(); }
/**
* Load all intial options.
*/
private static void load() {
DisplayTop = prefs.getInt("Display/Top", 100);
DisplayLeft = prefs.getInt("Display/Left", 100);
DisplayWidth = prefs.getInt("Display/Width", 800);
DisplayHeight = prefs.getInt("Display/Height", 600);
DisplayToolbar = prefs.getBoolean("Display/Toolbar", true);
CommandRun = prefs.get("Command/Run", "F5");
CommandFormat = prefs.get("Command/Format", "F6");
ViewLineNumbers = prefs.getBoolean("Options/ViewLineNumbers", true);
LambdaMode = prefs.getBoolean("Options/LambdaMode", false);
GreekMode = prefs.getBoolean("Options/GreekMode", false);
EmacsKeybindings = prefs.getBoolean("Options/EmacsKeybindings", false);
ConfirmOnRun = prefs.getBoolean("Options/ConfirmOnRun", true);
ConfirmOnClose = prefs.getBoolean("Options/ConfirmOnClose", true);
BackupOnSave = prefs.getBoolean("Options/BackupOnSave", true);
if (EmacsKeybindings)
EmacsKeyBindings.load();
Colors = new HashMap<String, Color>();
Colors.put("default", new Color(prefs.getInt("Colors/default", 0x000000)));
Colors.put("comment", new Color(prefs.getInt("Colors/comment", 0x006600)));
Colors.put("keyword", new Color(prefs.getInt("Colors/keyword", 0x000099)));
Colors.put("string", new Color(prefs.getInt("Colors/string", 0xFF8C00)));
Colors.put("bracket", new Color(prefs.getInt("Colors/bracket", 0x00FFFF)));
Colors.put("invalid-bracket", new Color(prefs.getInt("Colors/invalid-bracket", 0xFF0000)));
SavedHistory = prefs.get("SavedHistory", "");
Keywords = new HashMap<String, Integer>();
KeywordHelpURLs = new HashMap<String, String>();
FontSize = prefs.getInt("FontSize", 12);
calculateFont();
SchemeDocument.reload();
RecentDocumentManager.setFiles(prefs.get("RecentDocuments", null));
loadSyntax();
}
/**
* Load syntax options from file.
*/
private static void loadSyntax() {
try {
ClassLoader loader = Options.class.getClassLoader();
final Scanner s = new Scanner(new File(loader.getResource("lib/syntax.csv").toURI()));
String line;
String[] parts;
while (s.hasNextLine()) {
line = s.nextLine().trim();
if ("".equals(line) || line.charAt(0) == ';') continue;
parts = line.split(",");
if (parts.length == 2) { // old style: keyword,indent
Keywords.put(parts[0], Integer.parseInt(parts[1]));
} else if (parts.length == 4) { // new style: keyword,kind,indent,help URL
Keywords.put(parts[0], Integer.parseInt(parts[2]));
if (!parts[3].trim().isEmpty())
KeywordHelpURLs.put(parts[0], parts[3]);
}
}
s.close();
} catch(FileNotFoundException ex) {
ErrorManager.logError("Unable to find syntax file.");
return;
} catch (URISyntaxException e) {
ErrorManager.logError("Unable to find syntax file.");
return;
}
}
/**
* Determine how wide the font actually is per character.
*/
private static void calculateFont() {
Font = new Font("Monospaced", java.awt.Font.PLAIN, FontSize);
Component c = new Component(){ private static final long serialVersionUID = 366311035336037525L; };
FontMetrics fm = c.getFontMetrics(Font);
FontWidth = fm.charWidth(' ');
FontHeight = fm.getHeight();
}
/**
* Save preferences.
*/
public static void save() {
prefs.putInt("Display/Top", DisplayTop);
prefs.putInt("Display/Left", DisplayLeft);
prefs.putInt("Display/Width", DisplayWidth);
prefs.putInt("Display/Height", DisplayHeight);
prefs.putBoolean("Display/Toolbar", DisplayToolbar);
prefs.put("Command/Run", CommandRun);
prefs.put("Command/Format", CommandFormat);
prefs.putBoolean("Options/ViewLineNumbers", ViewLineNumbers);
prefs.putBoolean("Options/LambdaMode", LambdaMode);
prefs.putBoolean("Options/GreekMode", GreekMode);
prefs.putBoolean("Options/EmacsKeybindings", EmacsKeybindings);
prefs.putBoolean("Options/ConfirmOnRun", ConfirmOnRun);
prefs.putBoolean("Options/ConfirmOnClose", ConfirmOnClose);
prefs.putBoolean("Options/BackupOnSave", BackupOnSave);
for (String key : Colors.keySet())
prefs.putInt("Colors/" + key, Colors.get(key).getRGB());
prefs.putInt("FontSize", FontSize);
prefs.put("RecentDocuments", RecentDocumentManager.getFiles());
prefs.put("SavedHistory", MainFrame.getHistory());
}
/**
* Build the options menu (only build once).
* @return An options menu.
*/
public static JMenu buildOptionsMenu(final MainFrame main) {
if (optionsMenu == null) {
optionsMenu = new JMenu("Options");
// Display options
JCheckBoxMenuItem showLineNumber = new JCheckBoxMenuItem("Show line numbers", ViewLineNumbers);
showLineNumber.addItemListener(new ItemListener() {
public void itemStateChanged(ItemEvent arg0) {
ViewLineNumbers = ((JCheckBoxMenuItem) arg0.getSource()).isSelected();
SchemeDocument.reload();
DocumentManager.ReloadAll();
}
});
optionsMenu.add(showLineNumber);
final JCheckBoxMenuItem lambdaMode = new JCheckBoxMenuItem("\u03BB mode", LambdaMode);
final JCheckBoxMenuItem greekMode = new JCheckBoxMenuItem("Greek mode", GreekMode);
lambdaMode.addItemListener(new ItemListener() {
public void itemStateChanged(ItemEvent arg0) {
LambdaMode = ((JCheckBoxMenuItem) arg0.getSource()).isSelected();
if (LambdaMode) {
GreekMode = false;
greekMode.setSelected(false);
}
SchemeDocument.reload();
DocumentManager.ReloadAll();
}
});
optionsMenu.add(lambdaMode);
greekMode.addItemListener(new ItemListener() {
public void itemStateChanged(ItemEvent arg0) {
GreekMode = ((JCheckBoxMenuItem) arg0.getSource()).isSelected();
if (GreekMode) {
LambdaMode = false;
lambdaMode.setSelected(false);
}
SchemeDocument.reload();
DocumentManager.ReloadAll();
}
});
optionsMenu.add(greekMode);
JCheckBoxMenuItem emacsMode = new JCheckBoxMenuItem("Use emacs keybindings", EmacsKeybindings);
emacsMode.addItemListener(new ItemListener() {
public void itemStateChanged(ItemEvent arg0) {
EmacsKeybindings = ((JCheckBoxMenuItem) arg0.getSource()).isSelected();
if (EmacsKeybindings)
EmacsKeyBindings.load();
else
EmacsKeyBindings.unload();
}
});
optionsMenu.add(emacsMode);
JCheckBoxMenuItem toolbar = new JCheckBoxMenuItem("Display toolbar", DisplayToolbar);
toolbar.addItemListener(new ItemListener() {
public void itemStateChanged(ItemEvent arg0) {
DisplayToolbar = ((JCheckBoxMenuItem) arg0.getSource()).isSelected();
main.toggleToolbar(DisplayToolbar);
}
});
optionsMenu.add(toolbar);
optionsMenu.addSeparator();
// Confirm options.
JCheckBoxMenuItem confirmRun = new JCheckBoxMenuItem("Confirm on Run", ConfirmOnRun);
confirmRun.addItemListener(new ItemListener() {
public void itemStateChanged(ItemEvent arg0) {
ConfirmOnRun = ((JCheckBoxMenuItem) arg0.getSource()).isSelected();
}
});
optionsMenu.add(confirmRun);
JCheckBoxMenuItem confirmClose = new JCheckBoxMenuItem("Confirm on Close", ConfirmOnClose);
confirmClose.addItemListener(new ItemListener() {
public void itemStateChanged(ItemEvent arg0) {
ConfirmOnClose = ((JCheckBoxMenuItem) arg0.getSource()).isSelected();
}
});
optionsMenu.add(confirmClose);
JCheckBoxMenuItem backup = new JCheckBoxMenuItem("Backup on Save", BackupOnSave);
backup.addItemListener(new ItemListener() {
public void itemStateChanged(ItemEvent arg0) {
BackupOnSave = ((JCheckBoxMenuItem) arg0.getSource()).isSelected();
}
});
optionsMenu.add(backup);
optionsMenu.addSeparator();
// Display the dialog to add keywords to syntax highlighting / indentation depth.
// JMenuItem resetSyntax = new JMenuItem("Keywords");
// resetSyntax.addActionListener(new ActionListener() {
// @Override
// public void actionPerformed(ActionEvent e) {
// SyntaxDialog.show();
// }
// });
// optionsMenu.add(resetSyntax);
// Special option to reset colors to defaults.
JMenu colorMenu = new JMenu("Colors");
JMenuItem resetColors = new JMenuItem("Reset colors");
resetColors.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
Colors.put("default", new Color(0x000000));
Colors.put("comment", new Color(0x006600));
Colors.put("keyword", new Color(0x000099));
Colors.put("string", new Color(0xFF8C00));
Colors.put("bracket", new Color(0x00FFFF));
Colors.put("invalid-bracket", new Color(0x00FFFF));
SchemeDocument.reload();
DocumentManager.ReloadAll();
};
});
colorMenu.add(resetColors);
colorMenu.addSeparator();
// Special menu for choosing font colors.
for (final String key : Colors.keySet()) {
String fixed = ("" + key.charAt(0)).toUpperCase() + key.substring(1);
final JMenuItem item = new JMenuItem(fixed);
item.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
Color newColor = JColorChooser.showDialog(item, "Choose color for " + key, Colors.get(key));
Colors.put(key, newColor);
SchemeDocument.reload();
DocumentManager.ReloadAll();
}
});
colorMenu.add(item);
}
optionsMenu.add(colorMenu);
// Special menu for font size choices.
// 8 to 30 by 2s.
final JMenu fontSizeMenu = new JMenu("Font size");
for (int i = 8; i <= 30; i += 2) {
final int fontSize = i;
final JCheckBoxMenuItem item = new JCheckBoxMenuItem("" + fontSize, false);
if (fontSize == FontSize)
item.setSelected(true);
item.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
for (int i = 0; i < fontSizeMenu.getItemCount(); i++)
((JCheckBoxMenuItem) fontSizeMenu.getItem(i)).setSelected(false);
((JCheckBoxMenuItem) arg0.getSource()).setSelected(true);
FontSize = fontSize;
SchemeDocument.reload();
DocumentManager.ReloadAll();
calculateFont();
}
});
fontSizeMenu.add(item);
}
optionsMenu.add(fontSizeMenu);
}
return optionsMenu;
}
// Hide this.
private Options() {};
}