// Tregex/Tsurgeon, PreferencesPanel - a GUI for tree search and modification
// Copyright (c) 2007-2008 The Board of Trustees of
// The Leland Stanford Junior University. All Rights Reserved.
//
// 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.
//
// This code is a GUI interface to Tregex and Tsurgeon (which were
// written by Rogey Levy and Galen Andrew).
//
// For more information, bug reports, fixes, contact:
// Christopher Manning
// Dept of Computer Science, Gates 1A
// Stanford CA 94305-9010
// USA
// Support/Questions: parser-user@lists.stanford.edu
// Licensing: parser-support@lists.stanford.edu
// http://www-nlp.stanford.edu/software/tregex.shtml
package edu.stanford.nlp.trees.tregex.gui;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.GraphicsEnvironment;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JColorChooser;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingConstants;
import edu.stanford.nlp.trees.*;
/**
* Class for creating the preferences panel which holds user definable preferences (e.g., tree display size,
* highlight color) and syncs these preferences with the appropriate data structures. This class only needs to be
* instantiated once for a given instance of the gui.
*
* @author Anna Rafferty
*/
@SuppressWarnings("serial")
public class PreferencesPanel extends JDialog {
private static final String FONT_ERROR = "font";//error code if font size given is not an int > 0
private static final String HISTORY_ERROR = "history";//error code if history size is not an int >0
private static final String MAX_MATCH_ERROR = "maxMatch";//error code if history size is not an int >0
private TregexGUI gui;
final JButton highlightButton;
private JTextField setEncoding;//declared here because may change in different places
public PreferencesPanel(TregexGUI gui) {
super(gui, "Preferences");
this.gui = gui;
this.setResizable(false);
final JPanel prefPanel = new JPanel();
prefPanel.setLayout(new GridBagLayout());
//display prefs box
Box displayPrefs = Box.createVerticalBox();
displayPrefs.setBorder(BorderFactory.createTitledBorder("Display"));
JPanel displayOptions = new JPanel();
displayOptions.setLayout(new GridLayout(3,2,0,2));
JLabel historyLabel = new JLabel("Recent matches length: ");
final JTextField historySizeField =
new JTextField(Integer.toString(Preferences.getHistorySize()));
displayOptions.add(historyLabel);
displayOptions.add(historySizeField);
JLabel maxMatchesLabel = new JLabel("Max displayed trees: ");
final JTextField maxMatchesSizeField
= new JTextField(Integer.toString(Preferences.getMaxMatches()));
displayOptions.add(maxMatchesLabel);
displayOptions.add(maxMatchesSizeField);
JLabel highlightLabel = new JLabel("Highlight color:");
highlightButton = makeColorButton("Pick a new highlight color: ",
Preferences.getHighlightColor(), prefPanel);
highlightButton.putClientProperty("JButton.buttonType","icon");
displayOptions.add(highlightLabel);
displayOptions.add(highlightButton);
displayPrefs.add(displayOptions);
//tree display prefs box
Box treeDisplayPrefs = Box.createVerticalBox();
treeDisplayPrefs.setBorder(BorderFactory.createTitledBorder("Tree Display"));
JPanel treeDisplayOptions = new JPanel();
treeDisplayOptions.setLayout(new GridLayout(4,2));
JLabel fontName = new JLabel("Font: ");
final JComboBox fontPicker = new JComboBox(GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames());
fontPicker.setSelectedItem(Preferences.getFont());
JLabel sizeLabel = new JLabel("Font size: ");
final JTextField size =
new JTextField(Integer.toString(Preferences.getFontSize()));
treeDisplayOptions.add(fontName);
treeDisplayOptions.add(fontPicker);
treeDisplayOptions.add(sizeLabel);
treeDisplayOptions.add(size);
JLabel defaultColorLabel = new JLabel("Tree color: ");
final JButton defaultColorButton = makeColorButton("Pick a new tree color: ",
Preferences.getTreeColor(), prefPanel);
treeDisplayOptions.add(defaultColorLabel);
treeDisplayOptions.add(defaultColorButton);
JLabel matchedLabel = new JLabel("Matched node color: ");
final JButton matchedButton = makeColorButton("Pick a new color for matched nodes: ",
Preferences.getMatchedColor(),
prefPanel);
treeDisplayOptions.add(matchedLabel);
treeDisplayOptions.add(matchedButton);
//----add to tree display box
treeDisplayPrefs.add(treeDisplayOptions);
//advanced preferences - headfinder, tree reader factory
JPanel advOptions = new JPanel();
advOptions.setBorder(BorderFactory.createTitledBorder("Advanced "));
advOptions.setLayout(new GridLayout(3,2,0,4));
JLabel headfinderName = new JLabel("Head finder:");
final JComboBox headfinderPicker = new JComboBox(new String[] {"ArabicHeadFinder", "BikelChineseHeadFinder", "ChineseHeadFinder", "ChineseSemanticHeadFinder", "CollinsHeadFinder", "DybroFrenchHeadFinder", "LeftHeadFinder", "ModCollinsHeadFinder", "NegraHeadFinder", "SemanticHeadFinder", "SunJurafskyChineseHeadFinder", "TueBaDZHeadFinder"}); //
headfinderPicker.setEditable(true);
headfinderPicker.setSelectedItem(Preferences.getHeadFinder()
.getClass().getSimpleName());
JLabel treeReaderFactoryName = new JLabel("Tree reader factory:");
final JComboBox trfPicker = new JComboBox(new String[] {"ArabicTreeReaderFactory", "ArabicTreeReaderFactory.ArabicRawTreeReaderFactory", "CTBTreeReaderFactory", "Basic categories only (LabeledScoredTreeReaderFactory)", "FrenchTreeReaderFactory","NoEmptiesCTBTreeReaderFactory", "PennTreeReaderFactory", "TregexTreeReaderFactory" });
trfPicker.setEditable(true);
trfPicker.setSelectedItem(Preferences.getTreeReaderFactory()
.getClass().getSimpleName());
JLabel encodingLabel = new JLabel("Character encoding: ");
setEncoding = new JTextField(Preferences.getEncoding());
setEncoding.setPreferredSize(headfinderName.getPreferredSize());
advOptions.add(headfinderName);
advOptions.add(headfinderPicker);
advOptions.add(treeReaderFactoryName);
advOptions.add(trfPicker);
advOptions.add(encodingLabel);
advOptions.add(setEncoding);
//tsurgeon enabled box
final JCheckBox tsurgeonCheck = new JCheckBox("Enable Tsurgeon");
tsurgeonCheck.setSelected(Preferences.getEnableTsurgeon());
//matched portions only box
final JCheckBox matchPortion = new JCheckBox("Show only matched portions of tree");
matchPortion.setSelected(Preferences.getMatchPortionOnly());
//add everything
GridBagConstraints c = new GridBagConstraints();
c.ipady = 3;
c.fill = GridBagConstraints.BOTH;
c.weightx = 1;
c.gridwidth = GridBagConstraints.REMAINDER;
prefPanel.add(displayPrefs,c);
prefPanel.add(treeDisplayPrefs,c);
prefPanel.add(advOptions,c);
prefPanel.add(tsurgeonCheck,c);
c.gridheight = GridBagConstraints.REMAINDER;
prefPanel.add(matchPortion,c);
JButton[] options = new JButton[2];
JButton okay = new JButton("Okay");
JButton cancel = new JButton("Cancel");
options[1] = cancel;
options[0] = okay;
final JOptionPane prefPane = new JOptionPane();
prefPane.setMessage(prefPanel);
prefPane.setOptions(options);
prefPane.setOpaque(true);
this.setContentPane(prefPane);
this.getRootPane().setDefaultButton(okay);
//--------- wire buttons
okay.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
try {
//check appropriate headfinder/tree reader
HeadFinder hf = Preferences.lookupHeadFinder(headfinderPicker.getSelectedItem().toString());
if (hf == null) {
JOptionPane.showMessageDialog(PreferencesPanel.this, "Sorry, there was an error finding or instantiating the head finder. Please choose another head finder.", "Head Finder Error", JOptionPane.ERROR_MESSAGE);
throw new Exception("Headfinder error");
}
TreeReaderFactory trf = Preferences.lookupTreeReaderFactory(trfPicker.getSelectedItem().toString());
if (trf == null) {
JOptionPane.showMessageDialog(PreferencesPanel.this, "Sorry, there was an error finding or instantiating the tree reader factory. Please choose another tree reader factory.", "Tree Reader Factory Error", JOptionPane.ERROR_MESSAGE);
throw new Exception("Tree reader factory error");
}
//check appropriate number formats
Integer historySize = checkNumberFormat(historySizeField, PreferencesPanel.HISTORY_ERROR);
Integer maxMatchSize = checkNumberFormat(maxMatchesSizeField, PreferencesPanel.MAX_MATCH_ERROR);
Integer textSize = checkNumberFormat(size, PreferencesPanel.FONT_ERROR);
syncFromPrefPanel(fontPicker.getSelectedItem().toString(),
textSize,
((ColorIcon) defaultColorButton.getIcon()).getColor(),
((ColorIcon) matchedButton.getIcon()).getColor(),
((ColorIcon) highlightButton.getIcon()).getColor(),
historySize,
maxMatchSize,
tsurgeonCheck.isSelected(), matchPortion.isSelected(), hf, trf, setEncoding.getText().trim());
PreferencesPanel.this.setVisible(false);
} catch(NumberFormatException e) {
//System.out.println("Error is: " + e.getMessage());
if (e.getMessage() == PreferencesPanel.FONT_ERROR)
JOptionPane.showMessageDialog(prefPanel, "Please enter an integer greater than 0 for the font size.", "Font size error", JOptionPane.ERROR_MESSAGE);
else if (e.getMessage() == PreferencesPanel.HISTORY_ERROR)
JOptionPane.showMessageDialog(prefPanel, "Please enter an integer greater than or equal to 0 for the number of recent matches to remember.", "History size error", JOptionPane.ERROR_MESSAGE);
else if (e.getMessage() == PreferencesPanel.HISTORY_ERROR)
JOptionPane.showMessageDialog(prefPanel, "Please enter an integer greater than or equal to 0 for the maximum number of matches to display.", "Max Matches size error", JOptionPane.ERROR_MESSAGE);
else
JOptionPane.showMessageDialog(prefPanel, "Please check that the font size, max matches to display, and number of recent matches to remember are all integers greater than 0.", "Size error", JOptionPane.ERROR_MESSAGE);
} catch (Exception e) {
// ignore
}
}
});
cancel.addActionListener(arg0 -> PreferencesPanel.this.setVisible(false));
}
private static Integer checkNumberFormat(JTextField component, String errorType) throws NumberFormatException {
Integer number = null;
String txt = component.getText();
if(txt!= null && !"".equals(txt)) {
try {
number = Integer.parseInt(txt);
if(number <= 0)
throw new NumberFormatException(errorType);
} catch(NumberFormatException e) {//we catch and throw so that we catch both the one we threw and the one that could be thrown by Integer.parseInt
throw new NumberFormatException(errorType);
}
}
return number;
}
public static void alignLeft(JComponent box) {
for(Component comp: box.getComponents()) {
((JComponent) comp).setAlignmentX(Box.LEFT_ALIGNMENT);
}
}
private void syncFromPrefPanel(String font, Integer fontSize, Color treeColor, Color matchedColor, Color highlightColor,
Integer historySize, Integer maxMatches, boolean enableTsurgeon, boolean matchPortionOnly, HeadFinder hf, TreeReaderFactory trf, String encoding) {
Preferences.setFont(font);
Preferences.setFontSize(fontSize == null ? 0 : fontSize);
Preferences.setTreeColor(treeColor);
Preferences.setMatchedColor(matchedColor);
Preferences.setHighlightColor(highlightColor);
Preferences.setHistorySize(historySize == null ? 0 : historySize);
Preferences.setMaxMatches(maxMatches == null ? 0 : maxMatches);
Preferences.setEnableTsurgeon(enableTsurgeon);
Preferences.setMatchPortionOnly(matchPortionOnly);
Preferences.setHeadFinder(hf);
Preferences.setTreeReaderFactory(trf);
Preferences.setEncoding(encoding);
gui.loadPreferences();
}
void checkEncodingAndDisplay(String headFinder, String trf) {
boolean prompt = false;
String defaultEncoding = "";
String curEncoding = FileTreeModel.getCurEncoding();
if(isChinese(headFinder, trf)) {
if(!curEncoding.equals(FileTreeModel.DEFAULT_CHINESE_ENCODING)) {
prompt = true;
defaultEncoding = FileTreeModel.DEFAULT_CHINESE_ENCODING;
}
} else if(isNegra(headFinder, trf)) {
if(!curEncoding.equals(FileTreeModel.DEFAULT_NEGRA_ENCODING)) {
prompt = true;
defaultEncoding = FileTreeModel.DEFAULT_NEGRA_ENCODING;
}
} else if(!curEncoding.equals(FileTreeModel.DEFAULT_ENCODING)) {
prompt = true;
defaultEncoding = FileTreeModel.DEFAULT_ENCODING;
}
if(prompt) {
doEncodingPrompt(defaultEncoding, curEncoding);
}
}
private void doEncodingPrompt(final String encoding, final String oldEncoding) {
final JPanel encodingPanel = new JPanel();
encodingPanel.setLayout(new BoxLayout(encodingPanel, BoxLayout.PAGE_AXIS));
JLabel text = new JLabel("<html>A head finder or tree reader was selected that has the default encoding " + encoding
+ "; this differs from " + oldEncoding + ", which was being used. If the encoding is changed, all newly loaded" +
"treebanks will be read using the new encoding. Choosing an encoding that is not the true encoding of your tree " +
"files may cause errors and unexpected behavior.</html>");
//text.setBorder(BorderFactory.createLineBorder(Color.black));
text.setAlignmentX(SwingConstants.LEADING);
JPanel textPanel = new JPanel(new BorderLayout());
textPanel.setPreferredSize(new Dimension(100,100));
textPanel.add(text);
encodingPanel.add(textPanel);
encodingPanel.add(Box.createVerticalStrut(5));
final JOptionPane fileFilterDialog = new JOptionPane();
fileFilterDialog.setMessage(encodingPanel);
JButton[] options = new JButton[3];
JButton useNewEncoding = new JButton("Use " + encoding);
JButton useOldEncoding = new JButton("Use " + oldEncoding);
JButton useAnotherEncoding = new JButton("Use encoding...");
options[0] = useNewEncoding;
options[1] = useOldEncoding;
options[2] = useAnotherEncoding;
fileFilterDialog.setOptions(options);
final JDialog dialog = fileFilterDialog.createDialog(null, "Default encoding changed...");
useNewEncoding.addActionListener(arg0 -> {
FileTreeModel.setCurEncoding(encoding);
if(setEncoding == null)
System.out.println("encoding null!!");
setEncoding.setText(encoding);
dialog.setVisible(false);
});
useOldEncoding.addActionListener(e -> dialog.setVisible(false));
useAnotherEncoding.addActionListener(e -> {
//need to prompt for an encoding
dialog.setVisible(false);
alternateEncodingPrompt(encoding);
});
dialog.getRootPane().setDefaultButton(useNewEncoding);
dialog.pack();
dialog.setLocationRelativeTo(this);
dialog.setVisible(true);
}
/**
* Prompts the user to enter a new encoding for loading tree files
*/
private void alternateEncodingPrompt(String newDefaultEncoding) {
String response = (String) JOptionPane.showInputDialog(this,"Please enter a text encoding: ", "Set Encoding...",
JOptionPane.QUESTION_MESSAGE,null,null,newDefaultEncoding);
FileTreeModel.setCurEncoding(response.trim());
setEncoding.setText(response.trim());
}
/**
* Checks if the given head finder or tree reader factory are for Negra (German).
*/
static boolean isNegra(String headFinder, String trf) {
return headFinder.startsWith("Negra");
}
/**
* Checks if the given head finder or tree reader factory are for Chinese; if so, the font chosen in prefs
* will be overridden for a Chinese compatible font
*/
static boolean isChinese(String headFinder, String trf) {
return headFinder.startsWith("Chinese") || headFinder.startsWith("OldChinese") || trf.equalsIgnoreCase("CTBTreeReaderFactory") || trf.equalsIgnoreCase("NoEmptiesCTBTreeReaderFactory");
}
/**
* Checks if the given head finder or tree reader factory are for Arabic; if so, the font chosen in prefs
* will be overridden for a Arabic compatible font
*/
static boolean isArabic(String headFinder, String trf) {
return headFinder.startsWith("Arabic") || trf.startsWith("Arabic");
}
/**
* Makes a color choosing button that displays only an icon with a square of the given color
*/
public static JButton makeColorButton(final String promptText, Color iconColor, final JPanel parent) {
final ColorIcon icon = new ColorIcon(iconColor);
final JButton button = new JButton(icon);
button.addActionListener(arg0 -> {
Color newColor = JColorChooser.showDialog(parent,promptText, icon.getColor());
if (newColor != null) {
icon.setColor(newColor);
parent.repaint();
}
});
return button;
}
private static class ColorIcon implements Icon {
private static final int iconHeight = 8;
private static final int iconWidth = 15;
private Color color;
public ColorIcon(Color c) {
this.color = c;
}
public int getIconHeight() {
return iconHeight;
}
public int getIconWidth() {
return iconWidth;
}
public void setColor(Color c) {
this.color = c;
}
public Color getColor() {
return color;
}
public void paintIcon(Component c, Graphics g, int x, int y) {
Color oldColor = g.getColor();
g.setColor(color);
g.fillRect(x, y, getIconWidth(), getIconHeight());
g.setColor(oldColor);
}
} // end static class ColorIcon
}