/* * TreeAppearanceController.java * * Copyright (C) 2006-2014 Andrew Rambaut * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package figtree.panel; import jebl.evolution.trees.SortedRootedTree; import jebl.evolution.trees.Tree; import jebl.evolution.graphs.Node; import figtree.treeviewer.TreeViewer; import figtree.treeviewer.TreeViewerListener; import figtree.treeviewer.painters.LabelPainter; import figtree.treeviewer.decorators.*; import jebl.util.Attributable; import jam.controlpalettes.AbstractController; import jam.panels.OptionsPanel; import javax.swing.*; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import java.awt.*; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.util.*; import java.text.DecimalFormat; import java.text.NumberFormat; /** * @author Andrew Rambaut * @version $Id$ * * $HeadURL$ * * $LastChangedBy$ * $LastChangedDate$ * $LastChangedRevision$ */ public class TreeAppearanceController extends AbstractController { private static final String CONTROLLER_TITLE = "Appearance"; private static final String CONTROLLER_KEY = "appearance"; private static final String FOREGROUND_COLOUR_KEY = "foregroundColour"; private static final String BACKGROUND_COLOUR_KEY = "backgroundColour"; private static final String SELECTION_COLOUR_KEY = "selectionColour"; private static final String BRANCH_COLOR_ATTRIBUTE_KEY = "branchColorAttribute"; private static final String BRANCH_LINE_WIDTH_KEY = "branchLineWidth"; // The defaults if there is nothing in the preferences private static Color DEFAULT_FOREGROUND_COLOUR = Color.BLACK; private static Color DEFAULT_BACKGROUND_COLOUR = Color.WHITE; private static Color DEFAULT_SELECTION_COLOUR = new Color(90, 108, 128); private static float DEFAULT_BRANCH_LINE_WIDTH = 1.0f; private static final String FONT_NAME_KEY = "fontName"; private static final String FONT_SIZE_KEY = "fontSize"; private static final String FONT_STYLE_KEY = "fontStyle"; private static final String NUMBER_FORMATTING_KEY = "numberFormatting"; private static final String DISPLAY_ATTRIBUTE_KEY = "displayAttribute"; private static final String SIGNIFICANT_DIGITS_KEY = "significantDigits"; // The defaults if there is nothing in the preferences private static String DEFAULT_FONT_NAME = "sansserif"; private static int DEFAULT_FONT_SIZE = 6; private static int DEFAULT_FONT_STYLE = Font.PLAIN; private static String DEFAULT_NUMBER_FORMATTING = "#.####"; private static String DECIMAL_NUMBER_FORMATTING = "#.####"; private static String SCIENTIFIC_NUMBER_FORMATTING = "0.###E0"; public TreeAppearanceController(final TreeViewer treeViewer, String tipKey, final LabelPainter tipLabelPainter, String nodeKey, final LabelPainter nodeLabelPainter, String branchKey, final LabelPainter branchLabelPainter) { this(treeViewer, tipKey, tipLabelPainter, nodeKey, nodeLabelPainter, branchKey, branchLabelPainter, true, false); } public TreeAppearanceController(final TreeViewer treeViewer, String tipKey, final LabelPainter tipLabelPainter, String nodeKey, final LabelPainter nodeLabelPainter, String branchKey, final LabelPainter branchLabelPainter, boolean hideColouring, boolean ordering) { this.treeViewer = treeViewer; this.hideColouring = hideColouring; final AttributableDecorator branchDecorator = new AttributableDecorator(); branchDecorator.setPaintAttributeName("!color"); branchDecorator.setStrokeAttributeName("!stroke"); treeViewer.setBranchDecorator(branchDecorator, false); int foregroundRGB = DEFAULT_FOREGROUND_COLOUR.getRGB(); int backgroundRGB = DEFAULT_BACKGROUND_COLOUR.getRGB(); int selectionRGB = DEFAULT_SELECTION_COLOUR.getRGB(); float branchLineWidth = DEFAULT_BRANCH_LINE_WIDTH; treeViewer.setForeground(new Color(foregroundRGB)); treeViewer.setBackground(new Color(backgroundRGB)); treeViewer.setSelectionColor(new Color(selectionRGB)); treeViewer.setBranchStroke(new BasicStroke(branchLineWidth, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); this.tipKey = tipKey; this.nodeKey = nodeKey; this.branchKey = branchKey; final String defaultFontName = DEFAULT_FONT_NAME; final int defaultFontStyle = DEFAULT_FONT_STYLE; final int defaultFontSize = DEFAULT_FONT_SIZE; final String defaultNumberFormatting = DEFAULT_NUMBER_FORMATTING; tipLabelPainter.setFont(new Font(defaultFontName, defaultFontStyle, defaultFontSize)); tipLabelPainter.setNumberFormat(new DecimalFormat(defaultNumberFormatting)); nodeLabelPainter.setFont(new Font(defaultFontName, defaultFontStyle, defaultFontSize)); nodeLabelPainter.setNumberFormat(new DecimalFormat(defaultNumberFormatting)); branchLabelPainter.setFont(new Font(defaultFontName, defaultFontStyle, defaultFontSize)); branchLabelPainter.setNumberFormat(new DecimalFormat(defaultNumberFormatting)); optionsPanel = new OptionsPanel(); if (!hideColouring) { branchColourAttributeCombo = new JComboBox(new String[] { "No attributes" }); setupAttributes(treeViewer.getTrees()); branchColourAttributeCombo.addItemListener(new ItemListener() { public void itemStateChanged(ItemEvent itemEvent) { if (branchColourAttributeCombo.getSelectedIndex() == 0) { treeViewer.setBranchColouringDecorator(null, null); treeViewer.setBranchDecorator(branchDecorator, false); } else { Set<Node> nodes = new HashSet<Node>(); for (Tree tree : treeViewer.getTrees()) { for (Node node : tree.getNodes()) { nodes.add(node); } } String attribute = (String) branchColourAttributeCombo.getSelectedItem(); if (attribute != null && attribute.length() > 0) { if (attribute.endsWith("*")) { Decorator decorator = new DiscreteColourDecorator(); treeViewer.setBranchColouringDecorator(attribute.substring(0, attribute.length() - 2), decorator); treeViewer.setBranchDecorator(null, false); } else if (DiscreteColourDecorator.isDiscrete(attribute, nodes)) { Decorator decorator = new HSBDiscreteColourDecorator(attribute, nodes); treeViewer.setBranchColouringDecorator(null, null); treeViewer.setBranchDecorator(decorator, false); } else { Decorator decorator = new InterpolatingColourDecorator( new ContinuousScale(attribute, nodes), new Color(192, 16, 0), new Color(0, 16, 192)); treeViewer.setBranchColouringDecorator(null, null); treeViewer.setBranchDecorator(decorator, false); } } } } }); optionsPanel.addComponentWithLabel("Colour by:", branchColourAttributeCombo); } else { branchColourAttributeCombo = null; } branchLineWidthSpinner = new JSpinner(new SpinnerNumberModel(1.0, 0.01, 48.0, 1.0)); branchLineWidthSpinner.addChangeListener(new ChangeListener() { public void stateChanged(ChangeEvent changeEvent) { float lineWidth = ((Double) branchLineWidthSpinner.getValue()).floatValue(); treeViewer.setBranchStroke(new BasicStroke(lineWidth, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER)); } }); optionsPanel.addComponentWithLabel("Line Weight:", branchLineWidthSpinner); Font font = tipLabelPainter.getFont(); fontSizeSpinner = new JSpinner(new SpinnerNumberModel(font.getSize(), 0.01, 48, 1)); optionsPanel.addComponentWithLabel("Font Size:", fontSizeSpinner); fontSizeSpinner.addChangeListener(new ChangeListener() { public void stateChanged(ChangeEvent changeEvent) { final float size = ((Double) fontSizeSpinner.getValue()).floatValue(); Font font = tipLabelPainter.getFont().deriveFont(size); tipLabelPainter.setFont(font); font = nodeLabelPainter.getFont().deriveFont(size); nodeLabelPainter.setFont(font); font = branchLabelPainter.getFont().deriveFont(size); branchLabelPainter.setFont(font); } }); NumberFormat format = tipLabelPainter.getNumberFormat(); int digits = format.getMaximumFractionDigits(); digitsSpinner = new JSpinner(new SpinnerNumberModel(digits, 2, 14, 1)); digitsSpinner.addChangeListener(new ChangeListener() { public void stateChanged(ChangeEvent changeEvent) { final int digits = (Integer)digitsSpinner.getValue(); NumberFormat format = tipLabelPainter.getNumberFormat(); format.setMaximumFractionDigits(digits); tipLabelPainter.setNumberFormat(format); format = nodeLabelPainter.getNumberFormat(); format.setMaximumFractionDigits(digits); nodeLabelPainter.setNumberFormat(format); format = branchLabelPainter.getNumberFormat(); format.setMaximumFractionDigits(digits); branchLabelPainter.setNumberFormat(format); } }); if (!hideColouring) { treeViewer.addTreeViewerListener(new TreeViewerListener() { public void treeChanged() { setupAttributes(treeViewer.getTrees()); optionsPanel.repaint(); } public void treeSettingsChanged() { // nothing to do } }); } if (ordering) { orderCombo = new JComboBox(new String[]{"Off", SortedRootedTree.BranchOrdering.INCREASING_NODE_DENSITY.toString(), SortedRootedTree.BranchOrdering.DECREASING_NODE_DENSITY.toString()}); orderCombo.setOpaque(false); orderCombo.setSelectedItem(treeViewer.isOrderBranchesOn() ? treeViewer.getBranchOrdering().ordinal() + 1 : 0); orderCombo.addItemListener(new ItemListener() { public void itemStateChanged(ItemEvent itemEvent) { if (orderCombo.getSelectedIndex() == 0) { treeViewer.setOrderBranchesOn(false); } else { treeViewer.setOrderBranchesOn(true); treeViewer.setBranchOrdering(SortedRootedTree.BranchOrdering.values()[orderCombo.getSelectedIndex() - 1]); } } }); optionsPanel.addComponentWithLabel("Order:", orderCombo); } else { orderCombo = null; } } private void setupAttributes(Collection<? extends Tree> trees) { Object selected = branchColourAttributeCombo.getSelectedItem(); branchColourAttributeCombo.removeAllItems(); branchColourAttributeCombo.addItem("User Selection"); if (trees == null) { return; } for (Tree tree : trees) { for (String name : getAttributeNames(tree.getNodes())) { branchColourAttributeCombo.addItem(name); } } branchColourAttributeCombo.setSelectedItem(selected); } private String[] getAttributeNames(Collection<? extends Attributable> items) { Set<String> attributeNames = new TreeSet<String>(); for (Attributable item : items) { for (String name : item.getAttributeNames()) { if (!name.startsWith("!")) { Object attr = item.getAttribute(name); if (!(attr instanceof Object[])) { attributeNames.add(name); } else { boolean isColouring = true; Object[] array = (Object[])attr; boolean isIndex = true; for (Object element : array) { if (isIndex && !(element instanceof Integer) || !isIndex && !(element instanceof Double)) { isColouring = false; break; } isIndex = !isIndex; } if (isIndex) { // a colouring should finish on an index (which means isIndex should be false)... isColouring = false; } if (isColouring) { attributeNames.add(name + " *"); } } } } } String[] attributeNameArray = new String[attributeNames.size()]; attributeNames.toArray(attributeNameArray); return attributeNameArray; } public JComponent getTitleComponent() { return null; } public JPanel getPanel() { return optionsPanel; } public boolean isInitiallyVisible() { return false; } public void initialize() { // nothing to do } public void setSettings(Map<String,Object> settings) { // These settings don't have controls yet but they will! treeViewer.setForeground((Color)settings.get(CONTROLLER_KEY + "." + FOREGROUND_COLOUR_KEY)); treeViewer.setBackground((Color)settings.get(CONTROLLER_KEY + "." + BACKGROUND_COLOUR_KEY)); treeViewer.setSelectionColor((Color)settings.get(CONTROLLER_KEY + "." + SELECTION_COLOUR_KEY)); if (branchColourAttributeCombo != null) { branchColourAttributeCombo.setSelectedItem(settings.get(CONTROLLER_KEY+"."+BRANCH_COLOR_ATTRIBUTE_KEY)); } branchLineWidthSpinner.setValue((Double)settings.get(CONTROLLER_KEY + "." + BRANCH_LINE_WIDTH_KEY)); fontSizeSpinner.setValue(((Number)settings.get(tipKey+"."+FONT_SIZE_KEY)).doubleValue()); digitsSpinner.setValue((Integer)settings.get(tipKey+"."+SIGNIFICANT_DIGITS_KEY)); } public void getSettings(Map<String, Object> settings) { settings.put(CONTROLLER_KEY + "." + FOREGROUND_COLOUR_KEY, treeViewer.getForeground()); settings.put(CONTROLLER_KEY + "." + BACKGROUND_COLOUR_KEY, treeViewer.getBackground()); settings.put(CONTROLLER_KEY + "." + SELECTION_COLOUR_KEY, treeViewer.getSelectionPaint()); if (branchColourAttributeCombo != null) { settings.put(CONTROLLER_KEY + "." + BRANCH_COLOR_ATTRIBUTE_KEY, branchColourAttributeCombo.getSelectedItem().toString()); } settings.put(CONTROLLER_KEY + "." + BRANCH_LINE_WIDTH_KEY, branchLineWidthSpinner.getValue()); settings.put(tipKey+"."+FONT_SIZE_KEY, fontSizeSpinner.getValue()); settings.put(tipKey+"."+SIGNIFICANT_DIGITS_KEY, digitsSpinner.getValue()); } private final OptionsPanel optionsPanel; private final JComboBox branchColourAttributeCombo; private final JSpinner branchLineWidthSpinner; private final JSpinner fontSizeSpinner; private final JSpinner digitsSpinner; private final JComboBox orderCombo; private final TreeViewer treeViewer; private final String tipKey; private final String nodeKey; private final String branchKey; private final boolean hideColouring; }