/* * 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.util.Collection; import java.util.HashSet; import java.util.Hashtable; import java.util.Iterator; import javax.swing.JButton; import javax.swing.JCheckBox; 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 PrototypePanel extends JPanel implements ActionListener, ChangeListener { public static final long serialVersionUID = 1; protected static final boolean DEBUG = false; protected static final int HALF_GUTTER = 4, GUTTER = 2 * HALF_GUTTER; protected static JSlider fadeSlider = null, depthSlider = null; protected static JButton zoomSelButton = null, zoomMapButton = null; protected static JCheckBox zoomLockCheckBox = null; protected static JLabel fadeLabel = null, depthLabel = null, zoomSelLabel = null, zoomMapLabel = null; protected JPanel fadePanel = null, fadeInnerPanel = null, zoomPanel = null, zoomInnerPanel = null, linePanel = null; protected WidgetStack widgetStack = null; public PrototypePanel(DockWindow dw) { Insets halfGutterInsets = new Insets(HALF_GUTTER, HALF_GUTTER, HALF_GUTTER, HALF_GUTTER); fadeInnerPanel = new JPanel(); fadeInnerPanel.setLayout(new GridBagLayout()); fadeLabel = new JLabel(VueResources.getString("interactionTools.opacity"), SwingConstants.RIGHT); fadeLabel.setFont(tufts.vue.gui.GUI.LabelFace); addToGridBag(fadeInnerPanel, fadeLabel, 0, 0, 1, 1, GridBagConstraints.LINE_END, halfGutterInsets); fadeSlider = new JSlider(JSlider.HORIZONTAL, 0, 100, 100); JLabel label100 = new JLabel("100%"); JLabel label80 = new JLabel("80%"); JLabel label60 = new JLabel("60%"); JLabel label40 = new JLabel("40%"); JLabel label20 = new JLabel("20%"); JLabel label0 = new JLabel("0%"); Hashtable<Integer, JLabel> labelTable = 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); labelTable.put(new Integer(100), label100); labelTable.put(new Integer(80), label80); labelTable.put(new Integer(60), label60); labelTable.put(new Integer(40), label40); labelTable.put(new Integer(20), label20); labelTable.put(new Integer(0), label0); fadeSlider.setLabelTable(labelTable); fadeSlider.setPaintLabels(true); fadeSlider.setMinimumSize(fadeSlider.getPreferredSize()); fadeSlider.addChangeListener(this); fadeSlider.setToolTipText(VueResources.getString("interactionTools.opacity.toolTip")); addToGridBag(fadeInnerPanel, fadeSlider, 1, 0, 1, 1, GridBagConstraints.LINE_END, GridBagConstraints.HORIZONTAL, 1.0, 0.0, halfGutterInsets); depthLabel = new JLabel(VueResources.getString("interactionTools.depth"), SwingConstants.RIGHT); depthLabel.setFont(tufts.vue.gui.GUI.LabelFace); addToGridBag(fadeInnerPanel, depthLabel, 0, 1, 1, 1, GridBagConstraints.LINE_END, halfGutterInsets); depthSlider = new JSlider(JSlider.HORIZONTAL, 0, 5, 0); JLabel label1 = new JLabel("1"); JLabel label2 = new JLabel("2"); JLabel label3 = new JLabel("3"); JLabel label4 = new JLabel("4"); JLabel label5 = new JLabel("5"); DepthSelectionListener depthListener = new DepthSelectionListener(); labelTable = new Hashtable<Integer, JLabel>(); label0 = new JLabel(VueResources.getString("interactionTools.off")); label0.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); labelTable.put(new Integer(0), label0); labelTable.put(new Integer(1), label1); labelTable.put(new Integer(2), label2); labelTable.put(new Integer(3), label3); labelTable.put(new Integer(4), label4); labelTable.put(new Integer(5), label5); depthSlider.setLabelTable(labelTable); depthSlider.setPaintLabels(true); depthSlider.setSnapToTicks(true); depthSlider.setMinimumSize(depthSlider.getPreferredSize()); depthSlider.addChangeListener(depthListener); depthSlider.setToolTipText(VueResources.getString("interactionTools.depth.toolTip")); VUE.getSelection().addListener(depthListener); addToGridBag(fadeInnerPanel, depthSlider, 1, 1, 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.NONE, 1.0, 0.0, halfGutterInsets); zoomInnerPanel = new JPanel(); zoomInnerPanel.setLayout(new GridBagLayout()); zoomSelLabel = new JLabel(VueResources.getString("interactionTools.zoomSel.label"), SwingConstants.RIGHT); zoomSelLabel.setFont(tufts.vue.gui.GUI.LabelFace); addToGridBag(zoomInnerPanel, zoomSelLabel, 0, 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, 1, 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, 0, 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, 1, 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, 2, 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.addChangeListener(this); addToGridBag(zoomInnerPanel, zoomLockCheckBox, 3, 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.fadeAndDepthWidget"), fadePanel); widgetStack.addPane(VueResources.getString("interactionTools.zoomWidget"), zoomPanel); dw.setContent(widgetStack); 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); fadeLabel.setBackground(Color.CYAN); fadeLabel.setOpaque(true); depthLabel.setBackground(Color.CYAN); depthLabel.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); zoomPanel.setBackground(Color.YELLOW); zoomPanel.setOpaque(true); zoomInnerPanel.setBackground(Color.MAGENTA); zoomInnerPanel.setOpaque(true); linePanel.setBackground(Color.CYAN); linePanel.setOpaque(true); } setVisible(true); } public void finalize() { fadeSlider = null; zoomSelButton = null; zoomMapButton = null; zoomLockCheckBox = null; fadeLabel = null; depthLabel = null; zoomSelLabel = null; zoomMapLabel = null; fadePanel = null; fadeInnerPanel = null; zoomPanel = null; zoomInnerPanel = null; linePanel = null; widgetStack = null; } public static void zoomIfLocked() { if (zoomLockCheckBox.isSelected()) { zoom(); } } public static void zoom() { LWSelection selection = VUE.getSelection(); if (selection.size() == 0) { ZoomTool.setZoomFit(); } else { ZoomTool.setZoomFitRegion(VUE.getActiveViewer(), selection.getBounds(), 16, false); } } public static double getAlpha() { return (VUE.getSelection().size() == 0 ? 1.0 : ((double)fadeSlider.getValue()) / 100.0); } /* 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(), 16, false); } else if (source == zoomMapButton) { ZoomTool.setZoomFit(); } } /* ChangeListener method -- checkbox has been clicked */ public void stateChanged(ChangeEvent event) { Object source = event.getSource(); if (source == zoomLockCheckBox) { zoomIfLocked(); } else if (source == fadeSlider) { VUE.getActiveViewer().repaint(); } } /* 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); } static class DepthSelectionListener implements ChangeListener, 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 depthSelectionSlider public void stateChanged(ChangeEvent event) { JSlider source = (JSlider)event.getSource(); if (!source.getValueIsAdjusting()) { GUI.invokeAfterAWT(sliderMoved); } } // LWSelection.Listener method public void selectionChanged(LWSelection selection) { if (depthSlider.getValue() > 0 && !ignoreSelectionEvents) { // Changes to selection can't be made now; must be done after listener notification completes. GUI.invokeAfterAWT(selectionChanged); } else { PrototypePanel.zoomIfLocked(); } } Runnable sliderMoved = new Runnable() { public void run() { try { LWSelection guiSelection = VUE.getSelection(); int depth = depthSlider.getValue(); 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. findChildrenToDepth(userSelection, depth + 1); guiSelection.add(deepSelection.iterator()); } previousDepth = depth; PrototypePanel.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(); findChildrenToDepth(userSelection, depth + 1); // Set the GUI's selection to userSelection (it may have gotten smaller) and add deepSelection. guiSelection.setTo(userSelection); guiSelection.add(deepSelection); } PrototypePanel.zoomIfLocked(); } catch (Exception ex) { ex.printStackTrace(); // Log.error("exception in InteractionPanel.selectionChanged()", ex); } finally { ignoreSelectionEvents = false; } } }; protected void findChildrenToDepth(Collection<LWComponent> collection, int depth) { // Add each node to deepSelection. Iterator<LWComponent> nodes = collection.iterator(); while (nodes.hasNext()) { LWComponent node = nodes.next(); if (node.getClass() == LWNode.class) { if (!userSelection.contains(node)) { deepSelection.add(node); } if (depth > 1) { // Add each node's links to deepSelection. Iterator<LWComponent> links = (Iterator<LWComponent>)(node.getConnected().iterator()); while (links.hasNext()) { LWComponent link = links.next(); if (!userSelection.contains(link)) { deepSelection.add(link); } } // Add each node's child nodes to deepSelection. findChildrenToDepth(node.getLinked(), depth - 1); } } } } } }