/* * DefaultTreeViewer.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.painters.*; import jebl.evolution.graphs.Node; import jebl.evolution.taxa.Taxon; import jebl.evolution.trees.*; import figtree.treeviewer.decorators.Decorator; import figtree.treeviewer.treelayouts.TreeLayout; import jam.panels.StatusProvider; import javax.swing.*; import java.awt.*; import java.awt.print.PageFormat; import java.awt.print.PrinterException; import java.util.*; import java.util.List; import java.util.regex.PatternSyntaxException; /** * @author Andrew Rambaut * @version $Id$ * * $HeadURL$ * * $LastChangedBy$ * $LastChangedDate$ * $LastChangedRevision$ */ public class DefaultTreeViewer extends TreeViewer { private final static double ZOOM_SCALE = 0.02; private final static double VERTICAL_EXPANSION_SCALE = 0.02; private final static double ZOOM_POWER = 1.2; public DefaultTreeViewer() { this(null); } /** * Creates new TreeViewer */ public DefaultTreeViewer(JFrame frame) { this.frame = frame; setLayout(new BorderLayout()); this.treePane = new TreePane(); treePane.setAutoscrolls(true); //enable synthetic drag events treePane.addTreePaneListener(new TreePaneListener() { public void treePaneSettingsChanged() { fireTreeSettingsChanged(); } }); JScrollPane scrollPane = new JScrollPane(treePane, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); scrollPane.setMinimumSize(new Dimension(150, 150)); scrollPane.setBorder(null); viewport = scrollPane.getViewport(); add(scrollPane, BorderLayout.CENTER); // This overrides MouseListener and MouseMotionListener to allow selection in the TreePane - // It installs itself within the constructor. treePaneSelector = new TreePaneSelector(treePane); treePaneRollOver = new TreePaneRollOver(treePane); setFocusable(true); } public void setTree(Tree tree) { trees.clear(); addTree(tree); showTree(0); } public void setTrees(Collection<? extends Tree> trees) { this.trees.clear(); for (Tree tree : trees) { addTree(tree); } showTree(0); } public void addTree(Tree tree) { this.trees.add(tree); // if (treePane.getTipLabelPainter() != null) { // treePane.getTipLabelPainter().setupAttributes(trees); // } // // if (treePane.getBranchLabelPainter() != null) { // treePane.getBranchLabelPainter().setupAttributes(trees); // } // // if (treePane.getNodeLabelPainter() != null) { // treePane.getNodeLabelPainter().setupAttributes(trees); // } // // if (treePane.getNodeBarPainter() != null) { // treePane.getNodeBarPainter().setupAttributes(trees); // } // // if (treePane.getLegendPainter() != null) { // treePane.getLegendPainter().setupAttributes(trees); // } } public void addTrees(Collection<? extends Tree> trees) { int count = getTreeCount(); for (Tree tree : trees) { addTree(tree); } showTree(count); } public List<Tree> getTrees() { return trees; } public Tree getCurrentTree() { // return trees.get(currentTreeIndex); return treePane.getTree(); } public List<Tree> getTreesAsViewed() { List<Tree> treeAsDisplayed = new ArrayList<Tree>(); for (Tree originalTree : trees) { treeAsDisplayed.add(treePane.constructTransformedTree((RootedTree)originalTree)); } return treeAsDisplayed; } public RootedTree getSelectedSubtree() { return treePane.getSelectedSubtree(); } public int getCurrentTreeIndex() { return currentTreeIndex; } public int getTreeCount() { if (trees == null) return 0; return trees.size(); } public StatusProvider getStatusProvider() { return treePaneRollOver; } public void showTree(int index) { if (isRootingOn() && getRootingType() == TreePane.RootingType.USER_ROOTING) { JOptionPane.showMessageDialog(frame, "Cannot switch trees when user rooting option is on.\n" + "Turn this option off to switch trees", "Unable to switch trees", JOptionPane.ERROR_MESSAGE); return; } Tree tree = trees.get(index); if (tree instanceof RootedTree) { treePane.setTree((RootedTree)tree); } else { treePane.setTree(Utils.rootTheTree(tree)); } currentTreeIndex = index; fireTreeChanged(); } public void showNextTree() { if (currentTreeIndex < trees.size() - 1) { showTree(currentTreeIndex + 1); } } public void showPreviousTree() { if (currentTreeIndex > 0) { showTree(currentTreeIndex - 1); } } public void setTreeLayout(TreeLayout treeLayout) { treePane.setTreeLayout(treeLayout); fireTreeSettingsChanged(); } private boolean zoomPending = false; private double zoom = 0.0; private double verticalExpansion = 0.0; public void setZoom(double zoom) { double n = Math.max(treePane.getTree().getTaxa().size(), 50); this.zoom = Math.pow(zoom * n * ZOOM_SCALE, ZOOM_POWER); // this.zoom = zoom * MAX_ZOOM; refreshZoom(); } public void setVerticalExpansion(double verticalExpansion) { double n = Math.max(treePane.getTree().getTaxa().size(), 50); this.verticalExpansion = Math.pow(verticalExpansion * n * VERTICAL_EXPANSION_SCALE, ZOOM_POWER); // this.verticalExpansion = verticalExpansion * MAX_VERTICAL_EXPANSION; refreshZoom(); } public boolean verticalExpansionAllowed() { return !treePane.maintainAspectRatio(); } public void setTimeScale(TimeScale timeScale) { treePane.setTimeScale(timeScale); } private void refreshZoom() { setZoom(zoom, zoom + verticalExpansion); } private void setZoom(double xZoom, double yZoom) { Dimension viewportSize = viewport.getViewSize(); Point position = viewport.getViewPosition(); Dimension extentSize = viewport.getExtentSize(); double w = extentSize.getWidth() * (1.0 + xZoom); double h = extentSize.getHeight() * (1.0 + yZoom); Dimension newSize = new Dimension((int) w, (int) h); treePane.setPreferredSize(newSize); double cx = position.getX() + (0.5 * extentSize.getWidth()); double cy = position.getY() + (0.5 * extentSize.getHeight()); double rx = ((double) newSize.getWidth()) / viewportSize.getWidth(); double ry = ((double) newSize.getHeight()) / viewportSize.getHeight(); double px = (cx * rx) - (extentSize.getWidth() / 2.0); double py = (cy * ry) - (extentSize.getHeight() / 2.0); Point newPosition = new Point((int) px, (int) py); viewport.setViewPosition(newPosition); treePane.revalidate(); } public boolean hasSelection() { return treePane.hasSelection(); } public Set<Node> getSelectedNodes() { return treePane.getSelectedNodes(); } public Set<Node> getSelectedTips() { return treePane.getSelectedTips(); } public Set<Taxon> getSelectedTaxa() { return treePane.getSelectedTaxa(); } /** * Select taxa with a search string matching the currently displayed attribute * @param searchType * @param searchString * @param caseSensitive */ public void selectTaxa(TextSearchType searchType, String searchString, boolean caseSensitive) { String attributeName = "!name"; LabelPainter lp = treePane.getTipLabelPainter(); if (lp != null && lp.isVisible() && lp.getDisplayAttribute() != null) { attributeName = lp.getDisplayAttribute(); } selectTaxa(attributeName, searchType, searchString, caseSensitive); scrollToSelectedTips(); } public void selectTaxa(String attributeName, TextSearchType searchType, String searchString, boolean caseSensitive) { if (treePane.getTree() == null) { return; } treePane.clearSelection(); String query = searchString; if (searchType != TextSearchType.REG_EX) { query = (caseSensitive ? searchString : searchString.toUpperCase()); query = query.trim(); } Tree tree = treePane.getTree(); for (Node node : tree.getExternalNodes()) { Taxon taxon = tree.getTaxon(node); if (attributeName == null) { Object target = taxon.getName(); if (matchesItem(searchType, target, query, caseSensitive)) { treePane.addSelectedTip(node); break; } for (String name : taxon.getAttributeNames()) { target = taxon.getAttribute(name); if (matchesItem(searchType, target, query, caseSensitive)) { treePane.addSelectedTip(node); break; } } } else { Object target; if (attributeName.equals("!name")) { target = taxon.getName(); } else { target = taxon.getAttribute(attributeName); if (target == null) { // if we can't find the attribute on the taxon, try the node target = node.getAttribute(attributeName); } } if (matchesItem(searchType, target, query, caseSensitive)) { treePane.addSelectedTip(node); } } } } public void selectNodes(String attributeName, TextSearchType searchType, String searchString, boolean caseSensitive) { treePane.clearSelection(); String query = searchString; if (searchType != TextSearchType.REG_EX) { query = (caseSensitive ? searchString : searchString.toUpperCase()); query = query.trim(); } Tree tree = treePane.getTree(); for (Node node : tree.getNodes()) { if (attributeName == null) { for (String name : node.getAttributeNames()) { Object target = node.getAttribute(name); if (matchesItem(searchType, target, query, caseSensitive)) { treePane.addSelectedNode(node); break; } } } else { Object target = node.getAttribute(attributeName); if (matchesItem(searchType, target, query, caseSensitive)) { treePane.addSelectedNode(node); } } } } private boolean matchesItem(TextSearchType searchType, Object object, String query, boolean caseSensitive) { if (object != null) { String target = (caseSensitive ? object.toString() : object.toString().toUpperCase()); switch (searchType) { case CONTAINS: if (target.contains(query)) { return true; } break; case STARTS_WITH: if (target.startsWith(query)) { return true; } break; case ENDS_WITH: if (target.endsWith(query)) { return true; } break; case MATCHES: if (target.equals(query)) { return true; } break; case REG_EX: try { if (target.matches(query)) { return true; } } catch (PatternSyntaxException pse) { // ignore } break; } } return false; } public void selectTaxa(String attributeName, NumberSearchType searchType, Number searchValue) { treePane.clearSelection(); RootedTree tree = treePane.getTree(); for (Node node : tree.getExternalNodes()) { Object value = null; if (attributeName.equals("!length")) { value = tree.getLength(node); } else if (attributeName.equals("!height")) { value = tree.getHeight(node); } else { Taxon taxon = tree.getTaxon(node); value = taxon.getAttribute(attributeName); } if (matchesItem(value, searchType, searchValue)) { treePane.addSelectedTip(node); } } } public void selectNodes(String attributeName, NumberSearchType searchType, Number searchValue) { treePane.clearSelection(); RootedTree tree = treePane.getTree(); for (Node node : tree.getNodes()) { Object value = null; if (attributeName.equals("!length")) { value = tree.getLength(node); } else if (attributeName.equals("!height")) { value = tree.getHeight(node); } else { value = node.getAttribute(attributeName); } if (matchesItem(value, searchType, searchValue)) { treePane.addSelectedNode(node); } } } public void selectTaxa(Collection<String> taxonNames) { treePane.clearSelection(); RootedTree tree = treePane.getTree(); for (Node node : tree.getExternalNodes()) { Object value = null; if (taxonNames.contains(tree.getTaxon(node).getName())) { treePane.addSelectedTip(node); } } } private boolean matchesItem(Object item, NumberSearchType searchType, Number searchValue) { if (item != null && item instanceof Number) { Number value = (Number)item; switch (searchType) { case EQUALS: if (value.equals(searchValue)) { return true; } break; case EQUALS_OR_GREATER_THAN: if (value.doubleValue() >= searchValue.doubleValue()) { return true; } break; case EQUALS_OR_LESS_THAN: if (value.doubleValue() <= searchValue.doubleValue()) { return true; } break; case GREATER_THAN: if (value.doubleValue() > searchValue.doubleValue()) { return true; } break; case LESS_THAN: if (value.doubleValue() < searchValue.doubleValue()) { return true; } break; case NOT_EQUALS: if (!searchValue.equals(value)) { return true; } break; } } return false; } public void scrollToSelectedTips() { Set<Node> selectedTips = treePane.getSelectedTips(); if (selectedTips.size() > 0) { Point point = treePane.getLocationOfTip(selectedTips.iterator().next()); treePane.scrollPointToVisible(point); } } public void cartoonSelectedNodes() { treePane.cartoonSelectedNodes(); fireTreeSettingsChanged(); } public void collapseSelectedNodes() { treePane.collapseSelectedNodes(); fireTreeSettingsChanged(); } public void clearCollapsedNodes() { treePane.clearCollapsedNodes(); fireTreeSettingsChanged(); } public void hilightSelectedNodes(Color color) { treePane.hilightSelectedNodes(color); fireTreeSettingsChanged(); } public void clearHilighting() { treePane.clearHilightedNodes(); fireTreeSettingsChanged(); } public void rerootOnSelectedBranch() { treePane.rerootOnSelectedBranch(); fireTreeSettingsChanged(); } public void clearRooting() { treePane.clearRooting(); fireTreeSettingsChanged(); } public void rotateSelectedNode() { treePane.rotateSelectedNode(); fireTreeSettingsChanged(); } public void clearRotations() { treePane.clearSelectedNodeRotations(); fireTreeSettingsChanged(); } public void annotateSelectedNodes(String name, Object value) { treePane.annotateSelectedNodes(name, value); fireTreeSettingsChanged(); } public void annotateSelectedTips(String name, Object value) { treePane.annotateSelectedTips(name, value); fireTreeSettingsChanged(); } public void clearAnnotation(String name) { treePane.clearSelectedNodeAnnotation(name); treePane.clearSelectedTipAnnotation(name); fireTreeSettingsChanged(); } public void clearColouring() { treePane.clearSelectedNodeAnnotation("!color"); treePane.clearSelectedTipAnnotation("!color"); fireTreeSettingsChanged(); } public void selectAll() { if (treePaneSelector.getSelectionMode() == TreePaneSelector.SelectionMode.TAXA) { treePane.selectAllTaxa(); } else { treePane.selectAllNodes(); } } public void clearSelectedTaxa() { treePane.clearSelection(); } public void addTreeSelectionListener(TreeSelectionListener treeSelectionListener) { treePane.addTreeSelectionListener(treeSelectionListener); } public void removeTreeSelectionListener(TreeSelectionListener treeSelectionListener) { treePane.removeTreeSelectionListener(treeSelectionListener); } public TreePaneSelector.SelectionMode getSelectionMode() { return treePaneSelector.getSelectionMode(); } public void setSelectionMode(TreePaneSelector.SelectionMode selectionMode) { TreePaneSelector.SelectionMode oldSelectionMode = treePaneSelector.getSelectionMode(); if (selectionMode == oldSelectionMode) { return; } if (oldSelectionMode == TreePaneSelector.SelectionMode.TAXA) { treePane.selectNodesFromSelectedTips(); } else if (selectionMode == TreePaneSelector.SelectionMode.TAXA) { treePane.selectTipsFromSelectedNodes(); } else if (selectionMode == TreePaneSelector.SelectionMode.CLADE) { treePane.selectCladesFromSelectedNodes(); } treePaneSelector.setSelectionMode(selectionMode); } public void setDragMode(TreePaneSelector.DragMode dragMode) { treePaneSelector.setDragMode(dragMode); } // A load of delegated method calls through to treePane (which is now hidden outside the package). public void setTipLabelPainter(LabelPainter<Node> tipLabelPainter) { treePane.setTipLabelPainter(tipLabelPainter); // tipLabelPainter.setupAttributes(trees); } public void setNodeLabelPainter(LabelPainter<Node> nodeLabelPainter) { treePane.setNodeLabelPainter(nodeLabelPainter); // nodeLabelPainter.setupAttributes(trees); } public void setNodeBarPainter(NodeBarPainter nodeBarPainter) { treePane.setNodeBarPainter(nodeBarPainter); // nodeBarPainter.setupAttributes(trees); } public void setTipShapePainter(NodeShapePainter tipShapePainter) { treePane.setTipShapePainter(tipShapePainter); } public void setNodeShapePainter(NodeShapePainter nodeShapePainter) { treePane.setNodeShapePainter(nodeShapePainter); } public void setBranchLabelPainter(LabelPainter<Node> branchLabelPainter) { treePane.setBranchLabelPainter(branchLabelPainter); // branchLabelPainter.setupAttributes(trees); } public void addScalePainter(ScalePainter scalePainter) { treePane.addScalePainter(scalePainter); } public void removeScalePainter(ScalePainter scalePainter) { treePane.removeScalePainter(scalePainter); } public void setScaleGridPainter(ScaleGridPainter scaleGridPainter) { treePane.setScaleGridPainter(scaleGridPainter); } public void setLegendPainter(LegendPainter legendPainter) { treePane.setLegendPainter(legendPainter); // legendPainter.setupAttributes(trees); } public void setBranchDecorator(Decorator branchDecorator, boolean isGradient) { treePane.setBranchDecorator(branchDecorator, isGradient); } public void setBranchColouringDecorator(String branchColouringAttribute, Decorator branchColouringDecorator) { treePane.setBranchColouringDecorator(branchColouringAttribute, branchColouringDecorator); } public void setNodeBackgroundDecorator(Decorator nodeBackgroundDecorator) { treePane.setNodeBackgroundDecorator(nodeBackgroundDecorator); } public void setHilightingGradient(boolean hilightingGradient) { treePane.setHilightingGradient(hilightingGradient); } public void setSelectionColor(Color selectionColor) { treePane.setSelectionColor(selectionColor); } public Paint getSelectionPaint() { return treePane.getSelectionPaint(); } public void setBranchStroke(BasicStroke branchStroke) { treePane.setBranchStroke(branchStroke); } public boolean isTransformBranchesOn() { return treePane.isTransformBranchesOn(); } public TransformedRootedTree.Transform getBranchTransform() { return treePane.getBranchTransform(); } public void setTransformBranchesOn(boolean transformBranchesOn) { treePane.setTransformBranchesOn(transformBranchesOn); } public void setBranchTransform(TransformedRootedTree.Transform transform) { treePane.setBranchTransform(transform); } public boolean isOrderBranchesOn() { return treePane.isOrderBranchesOn(); } public SortedRootedTree.BranchOrdering getBranchOrdering() { return treePane.getBranchOrdering(); } public void setOrderBranchesOn(boolean orderBranchesOn) { treePane.setOrderBranchesOn(orderBranchesOn); } public void setBranchOrdering(SortedRootedTree.BranchOrdering branchOrdering) { treePane.setBranchOrdering(branchOrdering); } public boolean isRootingOn() { return treePane.isRootingOn(); } public TreePane.RootingType getRootingType() { return treePane.getRootingType(); } public void setRootingOn(boolean rootingOn) { treePane.setRootingOn(rootingOn); } public void setRootingType(TreePane.RootingType rootingType) { treePane.setRootingType(rootingType); } public void setToolMode(TreePaneSelector.ToolMode toolMode) { treePaneSelector.setToolMode(toolMode); } public JComponent getContentPane() { return treePane; } public void paint(Graphics g) { if( zoomPending ) { refreshZoom(); zoomPending = false; } super.paint(g); } public int print(Graphics g, PageFormat pageFormat, int pageIndex) throws PrinterException { return treePane.print(g, pageFormat, pageIndex); } public void addTreeViewerListener(TreeViewerListener listener) { listeners.add(listener); } public void removeTreeViewerListener(TreeViewerListener listener) { listeners.remove(listener); } public void fireTreeChanged() { for (TreeViewerListener listener : listeners) { listener.treeChanged(); } } public void fireTreeSettingsChanged() { for (TreeViewerListener listener : listeners) { listener.treeSettingsChanged(); } } private java.util.List<TreeViewerListener> listeners = new ArrayList<TreeViewerListener>(); private List<Tree> trees = new ArrayList<Tree>(); private int currentTreeIndex = 0; protected TreePane treePane; protected TreePaneSelector treePaneSelector; protected TreePaneRollOver treePaneRollOver; protected JViewport viewport; private final JFrame frame; }