/*
* 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;
}