/*
* TreeViewerController.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.treeviewer;
import figtree.treeviewer.treelayouts.*;
import jam.controlpalettes.AbstractController;
import jam.panels.OptionsPanel;
import jam.util.IconUtils;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Map;
import java.util.prefs.Preferences;
/**
* @author Andrew Rambaut
* @version $Id$
*
* $HeadURL$
*
* $LastChangedBy$
* $LastChangedDate$
* $LastChangedRevision$
*/
public class TreeViewerController extends AbstractController {
public enum TreeLayoutType {
RECTILINEAR("Rectangle"),
POLAR("Polar"),
RADIAL("Radial");
TreeLayoutType(String name) {
this.name = name;
}
public String toString() {
return name;
}
private final String name;
}
private static final String CONTROLLER_TITLE = "Layout";
private static Preferences PREFS = Preferences.userNodeForPackage(TreeViewerController.class);
private static final String CONTROLLER_KEY = "layout";
private static final String LAYOUT_KEY = "layoutType";
private static final String ZOOM_KEY = "zoom";
private static final String EXPANSION_KEY = "expansion";
// The defaults if there is nothing in the preferences
private static String DEFAULT_LAYOUT = TreeLayoutType.RECTILINEAR.name();
private final static int MAX_ZOOM_SLIDER = 1000;
private final static int DELTA_ZOOM_SLIDER = 1;
public TreeViewerController(final TreeViewer treeViewer) {
this.treeViewer = treeViewer;
final TreeLayoutType defaultLayout = TreeLayoutType.valueOf(PREFS.get(CONTROLLER_KEY + "." + LAYOUT_KEY, DEFAULT_LAYOUT));
titleLabel = new JLabel(CONTROLLER_TITLE);
optionsPanel = new ControllerOptionsPanel(2, 2);
rectilinearTreeLayout = new RectilinearTreeLayout();
rectilinearTreeLayoutController = new RectilinearTreeLayoutController(rectilinearTreeLayout);
polarTreeLayout = new PolarTreeLayout();
polarTreeLayoutController = new PolarTreeLayoutController(polarTreeLayout);
radialTreeLayout = new RadialTreeLayout();
radialTreeLayoutController = new RadialTreeLayoutController(radialTreeLayout);
JPanel panel1 = new JPanel();
panel1.setOpaque(false);
panel1.setLayout(new BoxLayout(panel1, BoxLayout.LINE_AXIS));
Icon rectangularTreeIcon = IconUtils.getIcon(this.getClass(), "images/rectangularTree.png");
Icon polarTreeIcon = IconUtils.getIcon(this.getClass(), "images/polarTree.png");
Icon radialTreeIcon = IconUtils.getIcon(this.getClass(), "images/radialTree.png");
rectangularTreeToggle = new JToggleButton(rectangularTreeIcon);
polarTreeToggle = new JToggleButton(polarTreeIcon);
radialTreeToggle = new JToggleButton(radialTreeIcon);
rectangularTreeToggle.setToolTipText("Rectangular tree layout");
polarTreeToggle.setToolTipText("Polar tree layout");
radialTreeToggle.setToolTipText("Radial tree layout");
rectangularTreeToggle.setFocusable(false);
polarTreeToggle.setFocusable(false);
radialTreeToggle.setFocusable(false);
rectangularTreeToggle.putClientProperty("Quaqua.Button.style", "toggleWest");
rectangularTreeToggle.putClientProperty("JButton.buttonType", "segmentedTextured");
rectangularTreeToggle.putClientProperty("JButton.segmentPosition", "first");
polarTreeToggle.putClientProperty("Quaqua.Button.style", "toggleCenter");
polarTreeToggle.putClientProperty("JButton.buttonType", "segmentedTextured");
polarTreeToggle.putClientProperty("JButton.segmentPosition", "middle");
radialTreeToggle.putClientProperty("Quaqua.Button.style", "toggleEast");
radialTreeToggle.putClientProperty("JButton.buttonType", "segmentedTextured");
radialTreeToggle.putClientProperty("JButton.segmentPosition", "last");
ButtonGroup buttonGroup = new ButtonGroup();
buttonGroup.add(rectangularTreeToggle);
buttonGroup.add(polarTreeToggle);
buttonGroup.add(radialTreeToggle);
rectangularTreeToggle.setSelected(true);
panel1.add(Box.createHorizontalGlue());
panel1.add(rectangularTreeToggle);
panel1.add(polarTreeToggle);
panel1.add(radialTreeToggle);
panel1.add(Box.createHorizontalGlue());
optionsPanel.addSpanningComponent(panel1);
zoomSlider = new JSlider(SwingConstants.HORIZONTAL, 0, MAX_ZOOM_SLIDER, 0);
zoomSlider.setOpaque(false);
zoomSlider.setAlignmentX(Component.LEFT_ALIGNMENT);
// zoomSlider.setPaintTicks(true);
// zoomSlider.setPaintLabels(true);
zoomSlider.setValue(0);
zoomSlider.addChangeListener(new ChangeListener() {
public void stateChanged(ChangeEvent changeEvent) {
final int value = zoomSlider.getValue();
treeViewer.setZoom(((double) value) / MAX_ZOOM_SLIDER);
}
});
optionsPanel.addComponentWithLabel("Zoom:", zoomSlider, true);
verticalExpansionSlider = new JSlider(SwingConstants.HORIZONTAL, 0, MAX_ZOOM_SLIDER, 0);
verticalExpansionSlider.setOpaque(false);
// verticalExpansionSlider.setPaintTicks(true);
// verticalExpansionSlider.setPaintLabels(true);
verticalExpansionSlider.setValue(0);
verticalExpansionSlider.addChangeListener(new ChangeListener() {
public void stateChanged(ChangeEvent changeEvent) {
final int value = verticalExpansionSlider.getValue();
treeViewer.setVerticalExpansion(((double) value) / MAX_ZOOM_SLIDER);
}
});
verticalExpansionLabel = new JLabel("Expansion:");
optionsPanel.addComponents(verticalExpansionLabel, false, verticalExpansionSlider, true);
fishEyeSlider = new JSlider(SwingConstants.HORIZONTAL, 0, MAX_ZOOM_SLIDER, 0);
fishEyeSlider.setOpaque(false);
fishEyeSlider.setValue(0);
fishEyeSlider.addChangeListener(new ChangeListener() {
public void stateChanged(ChangeEvent changeEvent) {
final double fishEye = ((double) fishEyeSlider.getValue()) / MAX_ZOOM_SLIDER;
rectilinearTreeLayout.setFishEye(fishEye);
polarTreeLayout.setFishEye(fishEye);
radialTreeLayout.setFishEye(fishEye);
}
});
fishEyeLabel = new JLabel("Fish Eye:");
optionsPanel.addComponents(fishEyeLabel, false, fishEyeSlider, true);
optionsPanel.addSeparator();
layoutPanel = new JPanel(new BorderLayout());
layoutPanel.setOpaque(false);
setTreeLayout(defaultLayout);
setExpansion();
optionsPanel.addSpanningComponent(layoutPanel);
rectangularTreeToggle.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent actionEvent) {
if (rectangularTreeToggle.isSelected()) setTreeLayout(TreeLayoutType.RECTILINEAR);
}
});
polarTreeToggle.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent actionEvent) {
if (polarTreeToggle.isSelected()) setTreeLayout(TreeLayoutType.POLAR);
}
});
radialTreeToggle.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent actionEvent) {
if (radialTreeToggle.isSelected()) setTreeLayout(TreeLayoutType.RADIAL);
}
});
// Set some InputMaps and ActionMaps for key strokes. The ActionMaps are set in setExpansion()
// because they differ by whether vertical expansion is allowed for the current layout.
// The key strokes could be obtained from preferences and set in a preference dialog box
optionsPanel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
KeyStroke.getKeyStroke("meta 0"), "resetZoom");
optionsPanel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
KeyStroke.getKeyStroke("meta EQUALS"), "increasePrimaryZoom");
optionsPanel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
KeyStroke.getKeyStroke("meta MINUS"), "decreasePrimaryZoom");
optionsPanel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
KeyStroke.getKeyStroke("meta alt EQUALS"), "increaseSecondaryZoom");
optionsPanel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
KeyStroke.getKeyStroke("meta alt MINUS"), "decreaseSecondaryZoom");
optionsPanel.getActionMap().put("resetZoom", resetZoomAction);
}
public JComponent getTitleComponent() {
return titleLabel;
}
public JPanel getPanel() {
return optionsPanel;
}
public boolean isInitiallyVisible() {
return true;
}
public void setColouringAttributeName(String attributeName) {
rectilinearTreeLayout.setBranchColouringAttributeName(attributeName);
polarTreeLayout.setBranchColouringAttributeName(attributeName);
radialTreeLayout.setBranchColouringAttributeName(attributeName);
}
public void initialize() {
// nothing to do
}
public void setSettings(Map<String,Object> settings) {
String treeLayoutName = (String)settings.get(CONTROLLER_KEY + "." + LAYOUT_KEY);
final TreeLayoutType layout = TreeLayoutType.valueOf(treeLayoutName);
switch (layout) {
case RECTILINEAR:
rectangularTreeToggle.setSelected(true);
break;
case POLAR:
polarTreeToggle.setSelected(true);
break;
case RADIAL:
radialTreeToggle.setSelected(true);
break;
}
setTreeLayout(layout);
zoomSlider.setValue((Integer)settings.get(CONTROLLER_KEY + "." + ZOOM_KEY));
verticalExpansionSlider.setValue((Integer)settings.get(CONTROLLER_KEY + "." + EXPANSION_KEY));
// These controllers are internal to TreeViewerController so settings must be done here
rectilinearTreeLayoutController.setSettings(settings);
polarTreeLayoutController.setSettings(settings);
radialTreeLayoutController.setSettings(settings);
}
public void getSettings(Map<String, Object> settings) {
if (rectangularTreeToggle.isSelected()) {
settings.put(CONTROLLER_KEY + "." + LAYOUT_KEY, TreeLayoutType.RECTILINEAR.name());
} else if (polarTreeToggle.isSelected()) {
settings.put(CONTROLLER_KEY + "." + LAYOUT_KEY, TreeLayoutType.POLAR.name());
} else if (radialTreeToggle.isSelected()) {
settings.put(CONTROLLER_KEY + "." + LAYOUT_KEY, TreeLayoutType.RADIAL.name());
}
settings.put(CONTROLLER_KEY + "." + ZOOM_KEY, zoomSlider.getValue());
settings.put(CONTROLLER_KEY + "." + EXPANSION_KEY, verticalExpansionSlider.getValue());
// These controllers are internal to TreeViewerController so settings must be done here
rectilinearTreeLayoutController.getSettings(settings);
polarTreeLayoutController.getSettings(settings);
radialTreeLayoutController.getSettings(settings);
}
private void setTreeLayout(TreeLayoutType layoutType) {
switch (layoutType) {
case RECTILINEAR:
treeViewer.setTreeLayout(rectilinearTreeLayout);
setExpansion();
fishEyeLabel.setEnabled(true);
fishEyeSlider.setEnabled(true);
layoutPanel.removeAll();
layoutPanel.add(rectilinearTreeLayoutController.getPanel(), BorderLayout.CENTER);
fireControllerChanged();
break;
case POLAR:
treeViewer.setTreeLayout(polarTreeLayout);
setExpansion();
fishEyeLabel.setEnabled(true);
fishEyeSlider.setEnabled(true);
layoutPanel.removeAll();
layoutPanel.add(polarTreeLayoutController.getPanel(), BorderLayout.CENTER);
fireControllerChanged();
break;
case RADIAL:
treeViewer.setTreeLayout(radialTreeLayout);
setExpansion();
fishEyeLabel.setEnabled(false);
fishEyeSlider.setEnabled(false);
layoutPanel.removeAll();
layoutPanel.add(radialTreeLayoutController.getPanel(), BorderLayout.CENTER);
fireControllerChanged();
break;
default:
new RuntimeException("Unknown TreeLayoutType: " + layoutType);
}
}
private void setExpansion() {
if (treeViewer.verticalExpansionAllowed()) {
verticalExpansionLabel.setEnabled(true);
verticalExpansionSlider.setEnabled(true);
optionsPanel.getActionMap().put("increasePrimaryZoom", increaseVerticalExpansionAction);
optionsPanel.getActionMap().put("decreasePrimaryZoom", decreaseVerticalExpansionAction);
optionsPanel.getActionMap().put("increaseSecondaryZoom", increaseZoomAction);
optionsPanel.getActionMap().put("decreaseSecondaryZoom", decreaseZoomAction);
} else {
verticalExpansionLabel.setEnabled(false);
verticalExpansionSlider.setEnabled(false);
optionsPanel.getActionMap().put("increasePrimaryZoom", increaseZoomAction);
optionsPanel.getActionMap().put("decreasePrimaryZoom", decreaseZoomAction);
optionsPanel.getActionMap().put("increaseSecondaryZoom", increaseZoomAction);
optionsPanel.getActionMap().put("decreaseSecondaryZoom", decreaseZoomAction);
}
}
private Action resetZoomAction = new AbstractAction("Reset Zoom") {
public void actionPerformed(ActionEvent actionEvent) {
zoomSlider.setValue(0);
verticalExpansionSlider.setValue(0);
}
};
private Action increaseZoomAction = new AbstractAction("Zoom In") {
public void actionPerformed(ActionEvent actionEvent) {
zoomSlider.setValue(zoomSlider.getValue() + DELTA_ZOOM_SLIDER);
}
};
private Action decreaseZoomAction = new AbstractAction("Zoom In") {
public void actionPerformed(ActionEvent actionEvent) {
zoomSlider.setValue(zoomSlider.getValue() - DELTA_ZOOM_SLIDER);
}
};
private Action increaseVerticalExpansionAction = new AbstractAction("Expand Vertically") {
public void actionPerformed(ActionEvent actionEvent) {
verticalExpansionSlider.setValue(verticalExpansionSlider.getValue() + DELTA_ZOOM_SLIDER);
}
};
private Action decreaseVerticalExpansionAction = new AbstractAction("Unexpand Vertically") {
public void actionPerformed(ActionEvent actionEvent) {
int value = verticalExpansionSlider.getValue();
if (value > 0) {
verticalExpansionSlider.setValue(value - DELTA_ZOOM_SLIDER);
} else {
// If the vertical expansion was zero then assume the user is trying to un-zoom
zoomSlider.setValue(zoomSlider.getValue() - DELTA_ZOOM_SLIDER);
}
}
};
private JToggleButton rectangularTreeToggle;
private JToggleButton polarTreeToggle;
private JToggleButton radialTreeToggle;
private JSlider zoomSlider;
private JSlider verticalExpansionSlider;
private JLabel verticalExpansionLabel;
private JSlider fishEyeSlider;
private JLabel fishEyeLabel;
private final JPanel layoutPanel;
private final JLabel titleLabel;
private final OptionsPanel optionsPanel;
private final RectilinearTreeLayout rectilinearTreeLayout;
private final PolarTreeLayout polarTreeLayout;
private final RadialTreeLayout radialTreeLayout;
private final RectilinearTreeLayoutController rectilinearTreeLayoutController;
private final PolarTreeLayoutController polarTreeLayoutController;
private final RadialTreeLayoutController radialTreeLayoutController;
private final TreeViewer treeViewer;
}