// PathVisio, // a tool for data visualization and analysis using Biological Pathways // Copyright 2006-2011 BiGCaT Bioinformatics // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // package org.pathvisio.desktop; import com.jgoodies.forms.builder.DefaultFormBuilder; import com.jgoodies.forms.layout.CellConstraints; import com.jgoodies.forms.layout.FormLayout; import com.jgoodies.forms.layout.RowSpec; import java.awt.BorderLayout; import java.awt.CardLayout; import java.awt.Color; import java.awt.Dimension; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.File; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.swing.BorderFactory; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JColorChooser; import javax.swing.JDialog; import javax.swing.JFileChooser; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JSplitPane; import javax.swing.JTextField; import javax.swing.JTree; import javax.swing.border.BevelBorder; import javax.swing.border.Border; import javax.swing.event.TreeSelectionEvent; import javax.swing.event.TreeSelectionListener; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.TreeNode; import javax.swing.tree.TreePath; import org.pathvisio.core.preferences.Preference; import org.pathvisio.core.preferences.PreferenceManager; import org.pathvisio.core.util.ColorConverter; import org.pathvisio.gui.SwingEngine; /** * Global dialog for setting the user preferences. */ public class PreferencesDlg { public static final String UPDATE_COMMAND = "prefDlg.updated"; private static final String ROOT_NODE_TITLE = "Preferences"; private Map panels = new HashMap (); private Set actionListeners = new HashSet(); private DefaultMutableTreeNode createNodes() { DefaultMutableTreeNode top = new DefaultMutableTreeNode(ROOT_NODE_TITLE); // sort child panels by name List panelTitles = new ArrayList(); panelTitles.addAll (panels.keySet()); Collections.sort (panelTitles); // organize child nodes DefaultMutableTreeNode prevNode = null; String prevTitle = null; for (String title : panelTitles) { if (prevTitle != null && title.startsWith(prevTitle + ".")) { prevNode.add (new DefaultMutableTreeNode (title)); } else { prevNode = new DefaultMutableTreeNode (title); prevTitle = title; top.add (prevNode); } } // add root node's preference panel last so that it's not treated as a child above panels.put(ROOT_NODE_TITLE, new PreferencePanel() { private JPanel panel = new JPanel(); public JPanel getPanel() { return panel; } public void apply() { } public void reset() { } }); return top; } /** * @param title The title of this panel, that will be visible in the JTree on the left * side of the dialog. You can use a dot (.) to group panels in the tree: for * example title "Display.Colors" will be arranged under "Display" in the tree (but "Display" * needs to exist). Grouping goes only one level deep. * @param panel use @link{PreferencePanel.builder()} to construct an @link{PreferencePanel}. * @throws IllegalArgumentException if title has already been claimed by another PreferencePanel */ public void addPanel (String title, PreferencePanel panel) { if (panels.containsKey(title) || ROOT_NODE_TITLE.equals(title)) { throw new IllegalArgumentException("Another panel has already been registered with the title '" + title + "'"); } panels.put (title, panel); } public void removePanel(PreferencePanel panel) { String key = null; for (Map.Entry e : panels.entrySet()) { if (panel == e.getValue()) { key = e.getKey(); break; } } if (key != null) { panels.remove(key); } } public DefaultPreferencePanel.Builder builder() { return new DefaultPreferencePanel.Builder(PreferenceManager.getCurrent()); } /** * PreferencePanel groups a number of preferences. *

* Use @link{PreferencePanel.builder()} to create one. * You can use the chained .xxxField() methods to add the preferences that you want to edit, * with a description. */ public static class DefaultPreferencePanel implements PreferencePanel, ActionListener { private JPanel panel; private PreferenceManager prefs; private List editors = new ArrayList(); public JPanel getPanel() { return panel; } public void apply() { for (FieldEditor editor : editors) { editor.apply(); } } public void restoreDefault() { for (FieldEditor editor : editors) { editor.restoreDefault(); } } public void reset() { for (FieldEditor editor : editors) { editor.reset(); } } private abstract class FieldEditor { protected Preference p; FieldEditor (Preference p) { this.p = p; } /** * Should copy the value of the editing component to * the preference */ abstract void apply(); /** * restoreDefault should set the editing component to * the default, but not apply it to the preference yet, * so that the user still has the chance to cancel. */ abstract void restoreDefault(); /** * Should copy the value of the preference * to the editing component. * Is called just before PreferenceDlg is shown. */ abstract void reset(); } private class BooleanFieldEditor extends FieldEditor { private JCheckBox cb; BooleanFieldEditor(Preference p, JCheckBox cb) { super(p); this.cb = cb; } @Override void apply() { prefs.setBoolean(p, cb.isSelected()); } @Override void restoreDefault() { cb.setSelected(p.getDefault().equals ("" + true)); } @Override void reset() { cb.setSelected (prefs.getBoolean (p)); } } private class ColorFieldEditor extends FieldEditor implements ActionListener { private JLabel colorLabel; ColorFieldEditor (Preference p, JLabel colorLabel) { super(p); this.colorLabel = colorLabel; colorLabel.setBorder(BorderFactory.createBevelBorder(BevelBorder.RAISED)); colorLabel.setOpaque(true); } public void actionPerformed (ActionEvent ae) { Color newColor = JColorChooser.showDialog(null, "Choose a color", prefs.getColor(p)); if (newColor != null) { colorLabel.setBackground(newColor); } } @Override void apply() { prefs.setColor(p, colorLabel.getBackground()); } @Override void restoreDefault() { Color c = ColorConverter.parseColorString(p.getDefault()); colorLabel.setForeground(c); colorLabel.setBackground(c); } @Override void reset() { Color c = prefs.getColor (p); colorLabel.setForeground(c); colorLabel.setBackground(c); } } private class IntegerFieldEditor extends FieldEditor { private JTextField txt; IntegerFieldEditor (Preference p, JTextField txt) { super(p); this.txt = txt; } @Override void apply() { try { prefs.set (p, "" + Integer.parseInt (txt.getText())); } catch (NumberFormatException e) { // ignore, we just leave the old value } } @Override void restoreDefault() { txt.setText (p.getDefault()); } @Override void reset() { txt.setText(prefs.get(p)); } } private class StringFieldEditor extends FieldEditor { private JTextField txt; StringFieldEditor (Preference p, JTextField txt) { super(p); this.txt = txt; } @Override void apply() { prefs.set (p, txt.getText()); } @Override void restoreDefault() { txt.setText (p.getDefault()); } @Override void reset() { txt.setText (prefs.get(p)); } } private class FileFieldEditor extends FieldEditor implements ActionListener { private JTextField txt; FileFieldEditor (Preference p, JTextField txt) { super(p); this.txt = txt; } public void actionPerformed(ActionEvent ae) { JFileChooser jfc = new JFileChooser(); if (jfc.showDialog(null, "Choose") == JFileChooser.APPROVE_OPTION) { File result = jfc.getSelectedFile(); txt.setText("" + result); prefs.setFile (p, result); } } @Override void apply() { prefs.set (p, txt.getText()); } @Override void restoreDefault() { txt.setText (p.getDefault()); } @Override void reset() { txt.setText (prefs.get(p)); } } public void actionPerformed(ActionEvent ae) { restoreDefault(); } /** Helper class to build preference panel, based on a list of preferences and their types. * Builds a panel and adds a textfield for a string, checkbox for a boolean, etc. *

* You can't instantiate directly. Use dlg.createBuilder * */ public static class Builder { private DefaultPreferencePanel result = new DefaultPreferencePanel(); private DefaultFormBuilder builder; private FormLayout layout; /** Can't instantiate directly. Use dlg.createBuilder */ private Builder(PreferenceManager prefs) { layout = new FormLayout("left:pref, 6dlu, 50dlu:grow, 4dlu, default"); builder = new DefaultFormBuilder(layout); result.prefs = prefs; } public PreferencePanel build() { JButton btnRestore = new JButton(); btnRestore.setText ("Restore Defaults"); btnRestore.addActionListener(result); // add button to the bottom-right of the panel CellConstraints cc = new CellConstraints(); builder.appendRow(RowSpec.decode("fill:pref:grow")); builder.add (btnRestore, cc.xyw(builder.getColumn(), builder.getRow(), 5, "right, bottom")); result.panel = builder.getPanel(); return result; } public Builder booleanField (Preference p, String desc) { JCheckBox cb = new JCheckBox (desc); BooleanFieldEditor editor = result.new BooleanFieldEditor (p, cb); builder.append (cb, 3); builder.nextLine(); result.editors.add(editor); return this; } public Builder colorField (Preference p, String desc) { JButton btnColor = new JButton("Change..."); JLabel colorLabel = new JLabel("--"); ColorFieldEditor editor = result.new ColorFieldEditor (p, colorLabel); btnColor.addActionListener(editor); builder.append (new JLabel (desc)); builder.append (colorLabel); builder.append (btnColor); builder.nextLine(); result.editors.add(editor); return this; } public Builder integerField (Preference p, String desc, int min, int max) { //TODO: handle min / max JTextField txt = new JTextField(8); IntegerFieldEditor editor = result.new IntegerFieldEditor (p, txt); builder.append (new JLabel (desc)); builder.append (txt); builder.nextLine(); result.editors.add(editor); return this; } public Builder stringField (Preference p, String desc) { JTextField txt = new JTextField(40); StringFieldEditor editor = result.new StringFieldEditor (p, txt); builder.append (new JLabel (desc)); builder.append (txt); builder.nextLine(); result.editors.add(editor); return this; } public Builder fileField (Preference p, String desc, boolean isDir) { //TODO: do something with isDir JTextField txt = new JTextField(40); JButton btnBrowse = new JButton("Browse"); FileFieldEditor editor = result.new FileFieldEditor (p, txt); btnBrowse.addActionListener(editor); builder.append (new JLabel (desc)); builder.append (txt); builder.append (btnBrowse); builder.nextLine(); result.editors.add(editor); return this; } } } PreferenceManager prefs; public PreferencesDlg (PreferenceManager prefs) { this.prefs = prefs; } /** * call this to open the dialog */ public void createAndShowGUI(SwingEngine swingEngine) { final JDialog dlg = new JDialog(swingEngine.getFrame(), ROOT_NODE_TITLE, true); dlg.setLayout (new BorderLayout()); DefaultMutableTreeNode top = createNodes(); JPanel pnlButtons = new JPanel(); JTree trCategories = new JTree(top) { // custom drawing of titles: only display part after . for subnodes. @Override public String convertValueToText(Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) { String title = "" + value; int pos = title.indexOf("."); if (pos < 0) return title; else return title.substring (pos + 1); } }; final JPanel pnlSettings = new JPanel(); JButton btnOk = new JButton(); btnOk.setText ("OK"); btnOk.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent ae) { for (PreferencePanel panel : panels.values()) { panel.apply(); } dlg.setVisible (false); dlg.dispose(); fireUpdateAction(); } } ); JButton btnCancel = new JButton(); btnCancel.setText ("Cancel"); btnCancel.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent ae) { dlg.setVisible(false); dlg.dispose(); } } ); final CardLayout cards = new CardLayout(); pnlSettings.setLayout (cards); for (String title : panels.keySet()) { PreferencePanel pp = panels.get(title); pp.reset(); pnlSettings.add (pp.getPanel(), title); } trCategories.addTreeSelectionListener(new TreeSelectionListener() { public void valueChanged(TreeSelectionEvent e) { TreePath tp = e.getPath(); String title = ((TreeNode)tp.getLastPathComponent()).toString(); cards.show(pnlSettings, title); } }); // set initial selection to root node trCategories.setSelectionPath(new TreePath(trCategories.getModel().getRoot())); pnlButtons.add (btnOk); pnlButtons.add (btnCancel); Border padBorder = BorderFactory.createEmptyBorder(5, 5, 5, 5); trCategories.setBorder(padBorder); pnlSettings.setBorder(padBorder); JScrollPane trScroll = new JScrollPane(trCategories); trScroll.setMinimumSize(new Dimension(100, 200)); JScrollPane pnlScroll = new JScrollPane(pnlSettings); pnlScroll.setMinimumSize(new Dimension(350, 200)); JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, trScroll, pnlScroll); dlg.add (splitPane); dlg.add (pnlButtons, BorderLayout.SOUTH); dlg.pack(); dlg.setLocationRelativeTo(swingEngine.getFrame()); dlg.setVisible (true); } public void addActionListener(ActionListener listener) { actionListeners.add(listener); } private void fireUpdateAction() { ActionEvent evt = new ActionEvent(this, ActionEvent.ACTION_PERFORMED, UPDATE_COMMAND); for (ActionListener listener : actionListeners) { listener.actionPerformed(evt); } } }