/* * Copyright 2003-2010 Tufts University Licensed under the * Educational Community 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.osedu.org/licenses/ECL-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 tufts.vue; import java.awt.Color; import java.awt.Component; import java.awt.Container; import java.awt.Dimension; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Hashtable; import java.util.Iterator; import java.util.List; import javax.swing.Icon; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComboBox; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JSlider; import javax.swing.SwingConstants; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import tufts.vue.gui.DockWindow; import tufts.vue.gui.GUI; import tufts.vue.gui.WidgetStack; public class InteractionTools extends JPanel implements ActionListener, ItemListener, ChangeListener, ExpandSelectionListener { public static final long serialVersionUID = 1; private static final org.apache.log4j.Logger Log = org.apache.log4j.Logger.getLogger(InteractionTools.class); protected static final boolean DEBUG = false; protected static final int HALF_GUTTER = 4, GUTTER = 2 * HALF_GUTTER, DEPTH_SLIDER_MIN = 0, DEPTH_SLIDER_MAX = 5; protected static final String ONE_HUNDRED_PERCENT = new String("100%"), EIGHTY_PERCENT = new String("80%"), SIXTY_PERCENT = new String("60%"), FOURTY_PERCENT = new String("40%"), TWENTY_PERCENT = new String("20%"), ZERO_PERCENT = new String("0%"), OFF = new String(VueResources.getString("interactionTools.off")), ONE = new String("1"), TWO = new String("2"), THREE = new String("3"), FOUR = new String("4"), FIVE = new String("5"); boolean ignoreSliderEvents = false; protected JSlider fadeSlider = null, depthSlider = null; protected JButton zoomSelButton = null, zoomMapButton = null; protected JCheckBox zoomLockCheckBox = null, incomingLinksCheckBox = null, outgoingLinksCheckBox = null; protected JLabel linksLabel = null, zoomSelLabel = null, zoomMapLabel = null; protected JPanel fadePanel = null, fadeInnerPanel = null, depthPanel = null, depthInnerPanel = null, linkDirectionPanel = null, zoomPanel = null, zoomInnerPanel = null, linePanel = null, depthSpacerPanel = null, zoomSpacerPanel = null; protected WidgetStack widgetStack = null; public InteractionTools(DockWindow dw) { Insets halfGutterInsets = new Insets(HALF_GUTTER, HALF_GUTTER, HALF_GUTTER, HALF_GUTTER); fadeInnerPanel = new JPanel(); fadeInnerPanel.setLayout(new GridBagLayout()); fadeSlider = new JSlider(JSlider.HORIZONTAL, 0, 100, 100); JLabel label100 = new JLabel(ONE_HUNDRED_PERCENT), label80 = new JLabel(EIGHTY_PERCENT), label60 = new JLabel(SIXTY_PERCENT), label40 = new JLabel(FOURTY_PERCENT), label20 = new JLabel(TWENTY_PERCENT), label0 = new JLabel(ZERO_PERCENT); Hashtable<Integer, JLabel> fadeTable = new Hashtable<Integer, JLabel>(); label100.setFont(tufts.vue.gui.GUI.LabelFace); label80.setFont(tufts.vue.gui.GUI.LabelFace); label60.setFont(tufts.vue.gui.GUI.LabelFace); label40.setFont(tufts.vue.gui.GUI.LabelFace); label20.setFont(tufts.vue.gui.GUI.LabelFace); label0.setFont(tufts.vue.gui.GUI.LabelFace); fadeTable.put(new Integer(100), label100); fadeTable.put(new Integer(80), label80); fadeTable.put(new Integer(60), label60); fadeTable.put(new Integer(40), label40); fadeTable.put(new Integer(20), label20); fadeTable.put(new Integer(0), label0); fadeSlider.setLabelTable(fadeTable); fadeSlider.setPaintLabels(true); fadeSlider.setSnapToTicks(false); fadeSlider.setMinimumSize(fadeSlider.getPreferredSize()); fadeSlider.setToolTipText(VueResources.getString("interactionTools.fade.toolTip")); fadeSlider.addChangeListener(this); addToGridBag(fadeInnerPanel, fadeSlider, 0, 0, 1, 1, GridBagConstraints.LINE_END, GridBagConstraints.HORIZONTAL, 1.0, 0.0, halfGutterInsets); fadePanel = new JPanel(); fadePanel.setLayout(new GridBagLayout()); addToGridBag(fadePanel, fadeInnerPanel, 0, 0, 1, 1, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, 1.0, 0.0, halfGutterInsets); depthInnerPanel = new JPanel(); depthInnerPanel.setLayout(new GridBagLayout()); depthSlider = new JSlider(JSlider.HORIZONTAL, DEPTH_SLIDER_MIN, DEPTH_SLIDER_MAX, DEPTH_SLIDER_MIN); JLabel labelOff = new JLabel(OFF), label1 = new JLabel(ONE), label2 = new JLabel(TWO), label3 = new JLabel(THREE), label4 = new JLabel(FOUR), label5 = new JLabel(FIVE); Hashtable<Integer, JLabel> depthTable = new Hashtable<Integer, JLabel>(); DepthSelectionListener depthListener = new DepthSelectionListener(); labelOff.setFont(tufts.vue.gui.GUI.LabelFace); label1.setFont(tufts.vue.gui.GUI.LabelFace); label2.setFont(tufts.vue.gui.GUI.LabelFace); label3.setFont(tufts.vue.gui.GUI.LabelFace); label4.setFont(tufts.vue.gui.GUI.LabelFace); label5.setFont(tufts.vue.gui.GUI.LabelFace); depthTable.put(new Integer(0), labelOff); depthTable.put(new Integer(1), label1); depthTable.put(new Integer(2), label2); depthTable.put(new Integer(3), label3); depthTable.put(new Integer(4), label4); depthTable.put(new Integer(5), label5); depthSlider.setLabelTable(depthTable); depthSlider.setPaintLabels(true); depthSlider.setSnapToTicks(true); depthSlider.setMinimumSize(depthSlider.getPreferredSize()); depthSlider.setToolTipText(VueResources.getString("interactionTools.depth.toolTip")); depthSlider.addChangeListener(depthListener); VUE.getSelection().addListener(depthListener); addToGridBag(depthInnerPanel, depthSlider, 0, 0, 1, 1, GridBagConstraints.LINE_END, GridBagConstraints.HORIZONTAL, 1.0, 0.0, halfGutterInsets); linkDirectionPanel = new JPanel(); linkDirectionPanel.setLayout(new GridBagLayout()); depthSpacerPanel = new JPanel(); addToGridBag(linkDirectionPanel, depthSpacerPanel, 0, 0, 1, 1, GridBagConstraints.LINE_START, GridBagConstraints.NONE, 0.0, 0.0, new Insets(0, 0, 0, 0)); linksLabel = new JLabel(VueResources.getString("interactionTools.linkDirection.label"), SwingConstants.RIGHT); linksLabel.setFont(tufts.vue.gui.GUI.LabelFace); addToGridBag(linkDirectionPanel, linksLabel, 1, 0, 1, 1, GridBagConstraints.LINE_START, GridBagConstraints.NONE, 0.0, 0.0, new Insets(0, 0, 0, HALF_GUTTER)); incomingLinksCheckBox = new JCheckBox(VueResources.getString("interactionTools.incomingLinks")); incomingLinksCheckBox.setSelected(true); incomingLinksCheckBox.setFont(tufts.vue.gui.GUI.LabelFace); incomingLinksCheckBox.setToolTipText(VueResources.getString("interactionTools.incomingLinks.toolTip")); incomingLinksCheckBox.addItemListener(depthListener); addToGridBag(linkDirectionPanel, incomingLinksCheckBox, 2, 0, 1, 1, GridBagConstraints.LINE_START, GridBagConstraints.NONE, 0.0, 0.0, new Insets(0, HALF_GUTTER, 0, HALF_GUTTER)); outgoingLinksCheckBox = new JCheckBox(VueResources.getString("interactionTools.outgoingLinks")); outgoingLinksCheckBox.setSelected(true); outgoingLinksCheckBox.setFont(tufts.vue.gui.GUI.LabelFace); outgoingLinksCheckBox.setToolTipText(VueResources.getString("interactionTools.outgoingLinks.toolTip")); outgoingLinksCheckBox.addItemListener(depthListener); addToGridBag(linkDirectionPanel, outgoingLinksCheckBox, 3, 0, 1, 1, GridBagConstraints.LINE_START, GridBagConstraints.NONE, 0.0, 0.0, new Insets(0, HALF_GUTTER, 0, 0)); addToGridBag(depthInnerPanel, linkDirectionPanel, 0, 1, 1, 1, GridBagConstraints.LINE_START, GridBagConstraints.NONE, 0.0, 0.0, halfGutterInsets); depthPanel = new JPanel(); depthPanel.setLayout(new GridBagLayout()); addToGridBag(depthPanel, depthInnerPanel, 0, 0, 1, 1, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, 1.0, 0.0, halfGutterInsets); zoomInnerPanel = new JPanel(); zoomInnerPanel.setLayout(new GridBagLayout()); zoomSpacerPanel = new JPanel(); addToGridBag(zoomInnerPanel, zoomSpacerPanel, 0, 0, 1, 2, GridBagConstraints.LINE_START, GridBagConstraints.NONE, 0.0, 0.0, new Insets(0, 0, 0, 0)); zoomSelLabel = new JLabel(VueResources.getString("interactionTools.zoomSel.label"), SwingConstants.RIGHT); zoomSelLabel.setFont(tufts.vue.gui.GUI.LabelFace); addToGridBag(zoomInnerPanel, zoomSelLabel, 1, 0, 1, 1, GridBagConstraints.LINE_END, halfGutterInsets); zoomSelButton = new JButton(); zoomSelButton.setFont(tufts.vue.gui.GUI.LabelFace); zoomSelButton.setText(VueResources.getString("interactionTools.zoomSel")); zoomSelButton.setToolTipText(VueResources.getString("interactionTools.zoomSel.toolTip")); zoomSelButton.addActionListener(this); addToGridBag(zoomInnerPanel, zoomSelButton, 2, 0, 1, 1, GridBagConstraints.LINE_END, GridBagConstraints.HORIZONTAL, 1.0, 0.0, halfGutterInsets); zoomMapLabel = new JLabel(VueResources.getString("interactionTools.zoomMap.label"), SwingConstants.RIGHT); zoomMapLabel.setFont(tufts.vue.gui.GUI.LabelFace); addToGridBag(zoomInnerPanel, zoomMapLabel, 1, 1, 1, 1, GridBagConstraints.LINE_END, halfGutterInsets); zoomMapButton = new JButton(); zoomMapButton.setFont(tufts.vue.gui.GUI.LabelFace); zoomMapButton.setText(VueResources.getString("interactionTools.zoomMap")); zoomMapButton.setToolTipText(VueResources.getString("interactionTools.zoomMap.toolTip")); zoomMapButton.addActionListener(this); addToGridBag(zoomInnerPanel, zoomMapButton, 2, 1, 1, 1, GridBagConstraints.LINE_END, GridBagConstraints.HORIZONTAL, 1.0, 0.0, halfGutterInsets); linePanel = new JPanel() { public static final long serialVersionUID = 1; protected void paintComponent(java.awt.Graphics g) { if (isOpaque()) { g.setColor(getBackground()); g.fillRect(0, 0, getWidth(), getHeight()); } g.setColor(java.awt.Color.DARK_GRAY); g.drawLine(0, 0, 0, getHeight()); g.drawLine(0, getHeight() / 2, getWidth() - 1, getHeight() / 2); } }; Dimension linePanelSize = new Dimension(2 * GUTTER, (2 * zoomMapButton.getPreferredSize().height) + GUTTER); linePanel.setPreferredSize(linePanelSize); linePanel.setMinimumSize(linePanelSize); addToGridBag(zoomInnerPanel, linePanel, 3, 0, 1, 2, halfGutterInsets); zoomLockCheckBox = new JCheckBox(VueResources.getString("interactionTools.auto")); zoomLockCheckBox.setFont(tufts.vue.gui.GUI.LabelFace); zoomLockCheckBox.setToolTipText(VueResources.getString("interactionTools.auto.toolTip")); zoomLockCheckBox.addItemListener(this); addToGridBag(zoomInnerPanel, zoomLockCheckBox, 4, 0, 1, 2, halfGutterInsets); zoomPanel = new JPanel(); zoomPanel.setLayout(new GridBagLayout()); addToGridBag(zoomPanel, zoomInnerPanel, 0, 0, 1, 1, GridBagConstraints.LINE_START, GridBagConstraints.NONE, 1.0, 0.0, halfGutterInsets); widgetStack = new WidgetStack(VueResources.getString("dockWindow.interactionTools.title")); widgetStack.addPane(VueResources.getString("interactionTools.fadeWidget"), fadePanel); widgetStack.addPane(VueResources.getString("interactionTools.depthWidget"), depthPanel); widgetStack.addPane(VueResources.getString("interactionTools.zoomWidget"), zoomPanel); dw.setContent(widgetStack); // Line up the labels on the depth and zoom widgets. Dimension linksPrefSize = linksLabel.getPreferredSize(); int depthExcessWidth = linksPrefSize.width - Math.max(zoomSelLabel.getPreferredSize().width, zoomMapLabel.getPreferredSize().width), depthPrefWidth = 0, zoomPrefWidth = 0; if (depthExcessWidth > 0) { // depth widget labels are wider zoomPrefWidth = depthExcessWidth; } else if (depthExcessWidth < 0){ // zoom widget labels are wider depthPrefWidth = -depthExcessWidth; } // otherwise, they are the same width Dimension depthPrefSize = new Dimension(depthPrefWidth, linksPrefSize.height), zoomPrefSize = new Dimension(zoomPrefWidth, linePanelSize.height); // Not only MUST minimum AND preferred sizes be set, they MUST be set IN THAT ORDER, and the panels MUST be opaque. depthSpacerPanel.setMinimumSize(depthPrefSize); depthSpacerPanel.setPreferredSize(depthPrefSize); depthSpacerPanel.setOpaque(true); zoomSpacerPanel.setMinimumSize(zoomPrefSize); zoomSpacerPanel.setPreferredSize(zoomPrefSize); zoomSpacerPanel.setOpaque(true); validate(); if (DEBUG) { fadeSlider.setBackground(Color.CYAN); fadeSlider.setOpaque(true); depthSlider.setBackground(Color.CYAN); depthSlider.setOpaque(true); zoomSelButton.setBackground(Color.CYAN); zoomSelButton.setOpaque(true); zoomMapButton.setBackground(Color.CYAN); zoomMapButton.setOpaque(true); zoomLockCheckBox.setBackground(Color.CYAN); zoomLockCheckBox.setOpaque(true); incomingLinksCheckBox.setOpaque(true); incomingLinksCheckBox.setBackground(Color.CYAN); outgoingLinksCheckBox.setOpaque(true); outgoingLinksCheckBox.setBackground(Color.CYAN); linkDirectionPanel.setOpaque(true); linkDirectionPanel.setBackground(Color.YELLOW); linksLabel.setBackground(Color.CYAN); linksLabel.setOpaque(true); zoomSelLabel.setBackground(Color.CYAN); zoomSelLabel.setOpaque(true); zoomMapLabel.setBackground(Color.CYAN); zoomMapLabel.setOpaque(true); fadePanel.setBackground(Color.YELLOW); fadePanel.setOpaque(true); fadeInnerPanel.setBackground(Color.MAGENTA); fadeInnerPanel.setOpaque(true); depthPanel.setBackground(Color.YELLOW); depthPanel.setOpaque(true); depthInnerPanel.setBackground(Color.MAGENTA); depthInnerPanel.setOpaque(true); zoomPanel.setBackground(Color.YELLOW); zoomPanel.setOpaque(true); zoomInnerPanel.setBackground(Color.MAGENTA); zoomInnerPanel.setOpaque(true); linePanel.setBackground(Color.CYAN); linePanel.setOpaque(true); depthSpacerPanel.setOpaque(true); depthSpacerPanel.setBackground(Color.GREEN); zoomSpacerPanel.setOpaque(true); zoomSpacerPanel.setBackground(Color.GREEN); } setVisible(true); } public void finalize() { fadeSlider = null; zoomSelButton = null; zoomMapButton = null; zoomLockCheckBox = null; incomingLinksCheckBox = null; outgoingLinksCheckBox = null; linksLabel = null; zoomSelLabel = null; zoomMapLabel = null; fadePanel = null; fadeInnerPanel = null; depthPanel = null; depthInnerPanel = null; linkDirectionPanel = null; zoomPanel = null; zoomInnerPanel = null; linePanel = null; depthSpacerPanel = null; zoomSpacerPanel = null; widgetStack = null; } protected void zoomIfLocked() { if (zoomLockCheckBox.isSelected()) { zoom(); } } protected void zoom() { LWSelection selection = VUE.getSelection(); if (selection.size() == 0) { ZoomTool.setZoomFit(); } else { ZoomTool.setZoomFitRegion(VUE.getActiveViewer(), selection.getBounds(), 32, false); } } public double getAlpha() { return (VUE.getSelection().size() == 0 ? 1.0 : ((double)fadeSlider.getValue()) / 100.0); } public boolean canExpand() { return depthSlider.getValue() < DEPTH_SLIDER_MAX; } public void doExpand() { depthSlider.setValue(depthSlider.getValue() + 1); } public boolean canShrink() { return depthSlider.getValue() > DEPTH_SLIDER_MIN; } public void doShrink() { depthSlider.setValue(depthSlider.getValue() - 1); } /* ActionListener method -- button has been clicked */ public void actionPerformed(ActionEvent event) { Object source = event.getSource(); if (source == zoomSelButton) { ZoomTool.setZoomFitRegion(VUE.getActiveViewer(), VUE.getSelection().getBounds(), 32, false); } else if (source == zoomMapButton) { ZoomTool.setZoomFit(); } } /* ItemListener method -- checkbox has been clicked */ public void itemStateChanged(ItemEvent event) { Object source = event.getSource(); if (source == zoomLockCheckBox) { zoomIfLocked(); } } /* ChangeListener method -- slider has been moved */ public void stateChanged(ChangeEvent event) { Object source = event.getSource(); if (source == fadeSlider) { VUE.getActiveViewer().repaint(); } } /* ExpandSelectionListener method */ public void depthChanged(ExpandSelectionEvent event) { ignoreSliderEvents = true; depthSlider.setValue(event.getDepth()); ignoreSliderEvents = false; } public void addExpandSelectionListener(ExpandSelectionListener tel) { listenerList.add(ExpandSelectionListener.class, tel); } public void removeExpandSelectionListener(ExpandSelectionListener tel) { listenerList.remove(ExpandSelectionListener.class, tel); } public ExpandSelectionListener[] getExpandSelectionListeners() { return (ExpandSelectionListener[])listenerList.getListeners( ExpandSelectionListener.class); } protected void fireDepthChanged(int depth) { if (!ignoreSliderEvents) { ExpandSelectionListener[] listeners = getExpandSelectionListeners(); ExpandSelectionEvent event = new ExpandSelectionEvent(this, depth); for (ExpandSelectionListener listener : listeners) { listener.depthChanged(event); } } } /* Static methods */ protected static void addToGridBag(Container container, Component component, int gridX, int gridY, int gridWidth, int gridHeight, Insets insets) { GridBagConstraints constraints = new GridBagConstraints(gridX, gridY, gridWidth, gridHeight, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, insets, 0, 0) ; ((GridBagLayout)container.getLayout()).setConstraints(component, constraints); container.add(component); } protected static void addToGridBag(Container container, Component component, int gridX, int gridY, int gridWidth, int gridHeight, int anchor, Insets insets) { GridBagConstraints constraints = new GridBagConstraints(gridX, gridY, gridWidth, gridHeight, 0.0, 0.0, anchor, GridBagConstraints.NONE, insets, 0, 0) ; ((GridBagLayout)container.getLayout()).setConstraints(component, constraints); container.add(component); } protected static void addToGridBag(Container container, Component component, int gridX, int gridY, int gridWidth, int gridHeight, int anchor, int fill, double weightX, double weightY, Insets insets) { GridBagConstraints constraints = new GridBagConstraints(gridX, gridY, gridWidth, gridHeight, weightX, weightY, anchor, fill, insets, 0, 0) ; ((GridBagLayout)container.getLayout()).setConstraints(component, constraints); container.add(component); } protected static void addToGridBag(Container container, Component component, int gridX, int gridY, int gridWidth, int gridHeight, int anchor, int fill, double weightX, double weightY, Insets insets, int padX, int padY) { GridBagConstraints constraints = new GridBagConstraints(gridX, gridY, gridWidth, gridHeight, weightX, weightY, anchor, fill, insets, padX, padY) ; ((GridBagLayout)container.getLayout()).setConstraints(component, constraints); container.add(component); } protected class DepthSelectionListener implements ChangeListener, ItemListener, LWSelection.Listener { HashSet<LWComponent> userSelection = new HashSet<LWComponent>(), // LWComponents selected by the user deepSelection = new HashSet<LWComponent>(); // LWComponents selected by this class int previousDepth = 0; boolean ignoreSelectionEvents = false; DepthSelectionListener() {} // ChangeListener method for depthSlider public void stateChanged(ChangeEvent event) { JSlider source = (JSlider)event.getSource(); if (!source.getValueIsAdjusting()) { fireDepthChanged(source.getValue()); GUI.invokeAfterAWT(sliderMoved); } } // ItemListener for link direction checkboxes public void itemStateChanged(ItemEvent event) { JCheckBox source = (JCheckBox)event.getSource(); if (source == incomingLinksCheckBox && !incomingLinksCheckBox.isSelected() && !outgoingLinksCheckBox.isSelected()) { outgoingLinksCheckBox.setSelected(true); } else if (source == outgoingLinksCheckBox && !outgoingLinksCheckBox.isSelected() && !incomingLinksCheckBox.isSelected()) { incomingLinksCheckBox.setSelected(true); } GUI.invokeAfterAWT(selectionChanged); } // LWSelection.Listener method public void selectionChanged(LWSelection selection) { if (!ignoreSelectionEvents) { if (depthSlider.getValue() > 0) { // Changes to selection can't be made until after listener notification completes, so invoke this later. GUI.invokeAfterAWT(selectionChanged); } else { zoomSelButton.setEnabled(selection.size() > 0); zoomIfLocked(); } } } Runnable sliderMoved = new Runnable() { public void run() { try { LWSelection guiSelection = VUE.getSelection(); int depth = depthSlider.getValue(); // ignoreSelectionEvents = true means that the selection events that will be coming up // are caused by deepening the selection and should be ignored -- ie, they shouldn't // cause the selection to be deepened further. ignoreSelectionEvents = true; if (previousDepth == 0) { // userSelection will be empty; set it to the GUI's current selection. userSelection.addAll(guiSelection); } else { // deepSelection will be recomputed below (if previousDepth is 0, it's already empty). deepSelection.clear(); if (depth < previousDepth) { // deepSelection will be smaller; reset the GUI's selection to userSelection. guiSelection.setTo(userSelection); } } if (depth == 0) { // Done with userSelection for now; empty it. userSelection.clear(); } else { // Find deepSelection and add it to the GUI's selection. Toolbox.findChildrenToDepth(userSelection, deepSelection, userSelection, depth, incomingLinksCheckBox.isSelected(), outgoingLinksCheckBox.isSelected(), new Hashtable<LWComponent, Integer>()); guiSelection.add(deepSelection.iterator()); } previousDepth = depth; zoomIfLocked(); } catch (Exception ex) { ex.printStackTrace(); Log.error("exception in InteractionPanel.sliderMoved()", ex); } finally { ignoreSelectionEvents = false; } } }; Runnable selectionChanged = new Runnable() { public void run() { try { LWSelection guiSelection = VUE.getSelection(); int depth = depthSlider.getValue(); ignoreSelectionEvents = true; if (depth > 0) { // Compute userSelection as the GUI's current selection minus deepSelection. userSelection.clear(); userSelection.addAll(guiSelection); Iterator<LWComponent> deepNodes = deepSelection.iterator(); while (deepNodes.hasNext()) { userSelection.remove(deepNodes.next()); } // Find deepSelection. deepSelection.clear(); Toolbox.findChildrenToDepth(userSelection, deepSelection, userSelection, depth, incomingLinksCheckBox.isSelected(), outgoingLinksCheckBox.isSelected(), new Hashtable<LWComponent, Integer>()); // Set the GUI's selection to userSelection (it may have gotten smaller) and add deepSelection. guiSelection.setTo(userSelection); guiSelection.add(deepSelection); } zoomSelButton.setEnabled(guiSelection.size() > 0); zoomIfLocked(); } catch (Exception ex) { ex.printStackTrace(); Log.error("exception in InteractionPanel.selectionChanged()", ex); } finally { ignoreSelectionEvents = false; } } }; } }