/******************************************************************************* * Mission Control Technologies, Copyright (c) 2009-2012, United States Government * as represented by the Administrator of the National Aeronautics and Space * Administration. All rights reserved. * * The MCT platform is licensed under the Apache 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.apache.org/licenses/LICENSE-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. * * MCT includes source code licensed under additional open source licenses. See * the MCT Open Source Licenses file included with this distribution or the About * MCT Licenses dialog available at runtime from the MCT Help menu for additional * information. *******************************************************************************/ package gov.nasa.arc.mct.fastplot.bridge; import gov.nasa.arc.mct.components.FeedProvider; import gov.nasa.arc.mct.fastplot.settings.LineSettings; import gov.nasa.arc.mct.fastplot.utils.AbbreviatingPlotLabelingAlgorithm; import gov.nasa.arc.mct.fastplot.utils.TruncatingLabel; import gov.nasa.arc.mct.fastplot.view.LegendEntryPopupMenuFactory; import gov.nasa.arc.mct.fastplot.view.legend.AbstractLegendEntry; import gov.nasa.arc.mct.util.LafColor; import java.awt.BasicStroke; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Font; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Shape; import java.awt.Stroke; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.font.FontRenderContext; import java.awt.font.TextAttribute; import java.util.ArrayList; import java.util.Hashtable; import java.util.List; import java.util.Map; import javax.swing.BorderFactory; import javax.swing.BoxLayout; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.ToolTipManager; import javax.swing.border.Border; import javax.swing.event.PopupMenuEvent; import javax.swing.event.PopupMenuListener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import plotter.xy.LinearXYPlotLine; /** * Legend entry for a plot line. The class responds to mouse entered events by increasing the brightness of the text labels. */ @SuppressWarnings("serial") public class LegendEntry extends JPanel implements MouseListener, AbstractLegendEntry { private final static Logger logger = LoggerFactory.getLogger(LegendEntry.class); // Padding around labels to create space between the label text and its outside edge // Add a little spacing from the left-hand side private static final int LEFT_PADDING = 5; private static final Border PANEL_PADDING = BorderFactory.createEmptyBorder(0, LEFT_PADDING, 0, 0); private static final Border EMPTY_BORDER = BorderFactory.createEmptyBorder(1,1,1,1); private Border focusBorder = BorderFactory.createLineBorder(LafColor.TEXT_HIGHLIGHT); // Associated plot. private LinearXYPlotLine linePlot; // Gui widgets protected JLabel baseDisplayNameLabel= new TruncatingLabel(); private Color backgroundColor; private Color foregroundColor; private Color originalPlotLineColor; private Stroke originalPlotLineStroke; private Stroke originalRegressionLineStroke; private Font originalFont; private Font boldFont; private Font strikeThruFont; private Font boldStrikeThruFont; private String baseDisplayName = ""; boolean selected=false; private String currentToolTipTxt = ""; private ToolTipManager toolTipManager; private String dataSetName = ""; private String thisBaseDisplayName = ""; private String valueString = ""; private AbbreviatingPlotLabelingAlgorithm plotLabelingAlgorithm = new AbbreviatingPlotLabelingAlgorithm(); private String computedBaseDisplayName = ""; private FeedProvider.RenderingInfo renderingInfo; private LegendEntryPopupMenuFactory popupManager = null; private LineSettings lineSettings = new LineSettings(); // Default width - will be adjusted to match base display name private int baseWidth = PlotConstants.PLOT_LEGEND_WIDTH; private LinearXYPlotLine regressionLine; /** * Construct a legend entry * @param theBackgroundColor background color of the entry * @param theForegroundColor text color * @param font text font */ public LegendEntry(Color theBackgroundColor, Color theForegroundColor, Font font, AbbreviatingPlotLabelingAlgorithm thisPlotLabelingAlgorithm) { setBorder(EMPTY_BORDER); setOpaque(false); plotLabelingAlgorithm = thisPlotLabelingAlgorithm; backgroundColor = theBackgroundColor; foregroundColor = theForegroundColor; setForeground(foregroundColor); lineSettings.setMarker(lineSettings.getColorIndex()); // Default to using same marker index as color index; may be later overridden if user-specified focusBorder = BorderFactory.createLineBorder(theForegroundColor); // NOTE: Original font size is 10. Decrease by 1 to size 9. // Need to explicitly cast to float from int on derived font size // Need to explicitly set to FontName=ArialMT/Arial-BoldMT and FontFamily=Arial to be // cross OS platforms L&F between MacOSX and Linux. // MacOSX defaults to Arial and Linux defaults to Dialog FontFamily. originalFont = font; originalFont = originalFont.deriveFont((float)(originalFont.getSize()-1)); boldFont = originalFont.deriveFont(Font.BOLD); Map<TextAttribute, Object> attributes = new Hashtable<TextAttribute, Object>(); attributes.put(TextAttribute.STRIKETHROUGH, TextAttribute.STRIKETHROUGH_ON); strikeThruFont = originalFont.deriveFont(attributes); boldStrikeThruFont = boldFont.deriveFont(attributes); // Setup the look of the labels. baseDisplayNameLabel.setBackground(backgroundColor); baseDisplayNameLabel.setForeground(foregroundColor); baseDisplayNameLabel.setFont(originalFont); baseDisplayNameLabel.setOpaque(true); // Sets as the default ToolTipManager toolTipManager = ToolTipManager.sharedInstance(); toolTipManager.setEnabled(true); toolTipManager.setLightWeightPopupEnabled(true); // Defaults: toolTipManager.getDismissDelay()=4000ms, // toolTipManager.getInitialDelay()=750ms, // toolTipManager.getReshowDelay()=500ms toolTipManager.setDismissDelay(PlotConstants.MILLISECONDS_IN_SECOND * 3); toolTipManager.setInitialDelay(PlotConstants.MILLISECONDS_IN_SECOND / 2); toolTipManager.setReshowDelay(PlotConstants.MILLISECONDS_IN_SECOND / 2); // Place the labels according to the format specified in the UE spec. layoutLabels(); // Listen to mouse events to drive the highlighting of legends when mouse enters. addMouseListener(this); } // Data getter and and setters void setPlot(LinearXYPlotLine thePlot) { linePlot = thePlot; updateLinePlotFromSettings(); } private List<String> getPanelOrWindowContextTitleList() { List<String> panelOrWindowContextTitleList = new ArrayList<String>(); panelOrWindowContextTitleList.clear(); if (plotLabelingAlgorithm != null) { if (plotLabelingAlgorithm.getPanelContextTitleList().size() > 0) { panelOrWindowContextTitleList.addAll(this.plotLabelingAlgorithm.getPanelContextTitleList()); } if (plotLabelingAlgorithm.getCanvasContextTitleList().size() > 0) { panelOrWindowContextTitleList.addAll(this.plotLabelingAlgorithm.getCanvasContextTitleList()); } } else { logger.error("Plot labeling algorithm object is NULL!"); } return panelOrWindowContextTitleList; } public void setBaseDisplayName(String theBaseDisplayName) { thisBaseDisplayName = theBaseDisplayName; if (thisBaseDisplayName != null) { thisBaseDisplayName = thisBaseDisplayName.trim(); } baseDisplayName = thisBaseDisplayName; // Format the base display name // Split string around newline character. String[] strings = baseDisplayName.split(PlotConstants.LEGEND_NEWLINE_CHARACTER); if (strings.length <= 1) { // Determine if first or second string is null if (baseDisplayName.indexOf(PlotConstants.LEGEND_NEWLINE_CHARACTER) == -1) { // first string is null. baseDisplayNameLabel.setText(baseDisplayName); } else if (theBaseDisplayName.equals(PlotConstants.LEGEND_NEWLINE_CHARACTER)) { baseDisplayNameLabel.setText(""); } else { // second string is empty. Truncate first. baseDisplayNameLabel.setText(PlotConstants.LEGEND_ELLIPSES); } } else { // Use table labeling algorithm to display base display name. // line1 is base display name; while line2 is PUI name String line1 = strings[0]; if (line1 != null) { baseDisplayName = line1.trim(); thisBaseDisplayName = baseDisplayName; } List<String> baseDisplayNameList = new ArrayList<String>(); baseDisplayNameList.add(line1); assert plotLabelingAlgorithm != null : "Plot labeling algorithm should NOT be NULL at this point."; baseDisplayName = plotLabelingAlgorithm.computeLabel(baseDisplayNameList, getPanelOrWindowContextTitleList()); // since this name will be used in a legend, it must not be empty so use the initial display name if it would have been empty if (baseDisplayName != null && baseDisplayName.isEmpty()) { baseDisplayName = thisBaseDisplayName; } computedBaseDisplayName = baseDisplayName; updateLabelText(); } valueString = formatNumericStringToScientificNotation(valueString); thisBaseDisplayName = theBaseDisplayName.replaceAll(PlotConstants.WORD_DELIMITERS, " "); currentToolTipTxt = "<HTML>" + thisBaseDisplayName.replaceAll(PlotConstants.LEGEND_NEWLINE_CHARACTER, "<BR>") + "<BR>" + valueString + "<HTML>"; this.setToolTipText(currentToolTipTxt); } private String formatNumericStringToScientificNotation(String valueString) { if (valueString != null && !valueString.isEmpty()) { valueString = PlotterPlot.getNumberFormatter(Double.parseDouble(valueString)).format(Double.parseDouble(valueString)); } return valueString; } void setData(FeedProvider.RenderingInfo info) { this.renderingInfo = info; String valueText = info.getValueText(); if (!"".equals(valueText)) { valueString = PlotConstants.DECIMAL_FORMAT.format(Double.parseDouble(valueText)); valueString = formatNumericStringToScientificNotation(valueString); } updateLabelFont(); updateLabelText(); thisBaseDisplayName = thisBaseDisplayName.replaceAll(PlotConstants.WORD_DELIMITERS, " "); currentToolTipTxt = "<HTML>" + thisBaseDisplayName.replaceAll(PlotConstants.LEGEND_NEWLINE_CHARACTER, "<BR>") + "<BR>" + valueString + "<HTML>"; this.setToolTipText(currentToolTipTxt); } private void updateLabelFont() { if(selected) { if(renderingInfo == null || renderingInfo.isPlottable()) { baseDisplayNameLabel.setFont(boldFont); } else { baseDisplayNameLabel.setFont(boldStrikeThruFont); } } else { if(renderingInfo == null || renderingInfo.isPlottable()) { baseDisplayNameLabel.setFont(originalFont); } else { baseDisplayNameLabel.setFont(strikeThruFont); } } } private void updateLabelText() { String statusText = renderingInfo == null ? null : renderingInfo.getStatusText(); if(statusText == null) { statusText = ""; } statusText = statusText.trim(); if(!"".equals(statusText)) { baseDisplayNameLabel.setText("(" + statusText + ") " + baseDisplayName); } else { baseDisplayNameLabel.setText(baseDisplayName); } } private void updateLabelWidth() { /* Record font & string to restore*/ Font f = baseDisplayNameLabel.getFont(); String s = baseDisplayNameLabel.getText(); if (f == originalFont && s.equals(baseDisplayName) && baseDisplayNameLabel.isValid()) { baseWidth = baseDisplayNameLabel.getWidth(); } } /** * Layout the labels within a legend in line with the UE specification. */ void layoutLabels() { setLayout(new BorderLayout()); JPanel panel = new JPanel(); panel.setBorder(PANEL_PADDING); panel.setBackground(backgroundColor); panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); JPanel displayNamePanel = new JPanel(); displayNamePanel.setLayout(new BoxLayout(displayNamePanel, BoxLayout.LINE_AXIS)); displayNamePanel.add(baseDisplayNameLabel); displayNamePanel.setAlignmentX(Component.LEFT_ALIGNMENT); panel.add(displayNamePanel); add(panel, BorderLayout.CENTER); } @Override public void mouseClicked(MouseEvent e) { // do nothing } @Override public void mouseEntered(MouseEvent e) { toolTipManager.registerComponent(this); if (!selected) { // Highlight this entry on the plot. originalPlotLineColor = linePlot.getForeground(); originalPlotLineStroke = linePlot.getStroke(); } selected = true; // Highlight this legend entry baseDisplayNameLabel.setForeground(foregroundColor.brighter()); updateLabelFont(); linePlot.setForeground(originalPlotLineColor.brighter().brighter()); if(originalPlotLineStroke == null) { linePlot.setStroke(new BasicStroke(PlotConstants.SELECTED_LINE_THICKNESS)); } else if (originalPlotLineStroke instanceof BasicStroke) { BasicStroke stroke = (BasicStroke) originalPlotLineStroke; linePlot.setStroke(new BasicStroke(stroke.getLineWidth() * PlotConstants.SELECTED_LINE_THICKNESS, stroke.getEndCap(), stroke .getLineJoin(), stroke.getMiterLimit(), stroke.getDashArray(), stroke.getDashPhase())); } //Otherwise, it's a stroke we can't change (ie EMPTY_STROKE) if (regressionLine != null) { originalRegressionLineStroke = regressionLine.getStroke(); regressionLine.setForeground(originalPlotLineColor.brighter().brighter()); Stroke stroke = (BasicStroke) regressionLine.getStroke(); //TODO synch with plot thickness feature changes if(stroke == null) { regressionLine.setStroke(new BasicStroke(PlotConstants.SLOPE_LINE_WIDTH*2, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10.0f, PlotConstants.dash1, 0.0f)); } else { regressionLine.setStroke(new BasicStroke(PlotConstants.SLOPE_LINE_WIDTH*2, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10.0f, PlotConstants.dash1, 0.0f)); } } this.setToolTipText(currentToolTipTxt); } @Override public void mouseExited(MouseEvent e) { toolTipManager.unregisterComponent(this); selected = false; // Return this legend entry to its original look. baseDisplayNameLabel.setForeground(foregroundColor); updateLabelFont(); // Return this entry on the plot to its original look. linePlot.setForeground(originalPlotLineColor); linePlot.setStroke(originalPlotLineStroke); if (regressionLine != null) { regressionLine.setForeground(originalPlotLineColor); regressionLine.setStroke(originalRegressionLineStroke); } } @Override public void mousePressed(MouseEvent e) { // open the color changing popup if (popupManager != null && e.isPopupTrigger()) { setBorder(focusBorder); //TODO: Externalize the color of this? JPopupMenu popup = popupManager.getPopup(this); popup.show(this, e.getX(), e.getY()); popup.addPopupMenuListener(new PopupMenuListener() { @Override public void popupMenuCanceled(PopupMenuEvent arg0) { setBorder(EMPTY_BORDER); } @Override public void popupMenuWillBecomeInvisible(PopupMenuEvent arg0) { setBorder(EMPTY_BORDER); } @Override public void popupMenuWillBecomeVisible(PopupMenuEvent arg0) { } }); } } @Override public void mouseReleased(MouseEvent e) { if (popupManager != null && e.isPopupTrigger()) { popupManager.getPopup(this).show(this, e.getX(), e.getY()); } } public String getToolTipText() { return currentToolTipTxt; } // Retrieves whatever is set in the label text field public String getBaseDisplayNameLabel() { return baseDisplayNameLabel.getText(); } // Retrieves base display name + PUI name public String getFullBaseDisplayName() { return thisBaseDisplayName; } // Retrieves only base display name (w/o PUI name) // after running thru labeling algorithm public String getComputedBaseDisplayName() { return computedBaseDisplayName; } // Retrieves truncated with ellipse public String getTruncatedBaseDisplayName() { return baseDisplayName; } public void setPlotLabelingAlgorithm(AbbreviatingPlotLabelingAlgorithm plotLabelingAlgorithm) { this.plotLabelingAlgorithm = plotLabelingAlgorithm; } public AbbreviatingPlotLabelingAlgorithm getPlotLabelingAlgorithm() { return this.plotLabelingAlgorithm; } public int getLabelWidth() { updateLabelWidth(); return baseWidth + LEFT_PADDING; } @Override public void setForeground(Color fg) { Color lineColor = fg; Color labelColor = fg; if (linePlot != null) { if (linePlot.getForeground() != foregroundColor) lineColor = fg; linePlot.setForeground(lineColor); } if (regressionLine != null) { if (regressionLine.getForeground() != foregroundColor) lineColor = fg.brighter().brighter(); regressionLine.setForeground(lineColor); } if (baseDisplayNameLabel != null) { if (baseDisplayNameLabel.getForeground() != foregroundColor) labelColor = fg.brighter(); baseDisplayNameLabel.setForeground(labelColor); } foregroundColor = fg; focusBorder = BorderFactory.createLineBorder(fg); // Infer the appropriate index for this color for (int i = 0; i < PlotConstants.MAX_NUMBER_OF_DATA_ITEMS_ON_A_PLOT; i++) { if (PlotLineColorPalette.getColor(i).getRGB() == fg.getRGB()) { lineSettings.setColorIndex(i); } } super.setForeground(fg); } public LegendEntryPopupMenuFactory getPopup() { return popupManager; } public void setPopup(LegendEntryPopupMenuFactory popup) { this.popupManager = popup; } public void setLineSettings(LineSettings settings) { lineSettings = settings; updateLinePlotFromSettings(); } public LineSettings getLineSettings() { return lineSettings; } private void updateLinePlotFromSettings() { /* Color */ int index = lineSettings.getColorIndex(); Color c = PlotLineColorPalette.getColor(index); setForeground(c); /* Thickness */ Stroke s = linePlot.getStroke(); if (s == null || s instanceof BasicStroke) { int t = lineSettings.getThickness(); linePlot.setStroke(t == 1 ? null : new BasicStroke(t)); originalPlotLineStroke = linePlot.getStroke(); } // We only want to modify known strokes /* Marker */ if (linePlot.getPointIcon() != null) { Shape shape = null; if (lineSettings.getUseCharacter()) { Graphics g = getGraphics(); if (g != null && g instanceof Graphics2D) { FontRenderContext frc = ((Graphics2D)g).getFontRenderContext(); shape = PlotLineShapePalette.getShape(lineSettings.getCharacter(), frc); } } else { int marker = lineSettings.getMarker(); shape = PlotLineShapePalette.getShape(marker); } if (shape != null) { linePlot.setPointIcon(new PlotMarkerIcon(shape)); baseDisplayNameLabel.setIcon(new PlotMarkerIcon(shape, false, 12, 12)); } } linePlot.repaint(); repaint(); } /** Get whether a regression line is displayed or not. * @return regressionLine */ public boolean hasRegressionLine() { return lineSettings.getHasRegression(); } /** Set whether a regression line is displayed or not. * @param regressionLine boolean indicator */ public void setHasRegressionLine(boolean regressionLine) { lineSettings.setHasRegression(regressionLine); } /** Get the number of regression points to use. * @return numberRegressionPoints the number of regression points to use */ public int getNumberRegressionPoints() { return lineSettings.getRegressionPoints(); } /** Set the number of regression points to use. * @param numberRegressionPoints */ public void setNumberRegressionPoints(int numberRegressionPoints) { lineSettings.setRegressionPoints(numberRegressionPoints); } /** Get the regression line for this legend entry. * @return regressionLine a LinearXYPlotLine */ public LinearXYPlotLine getRegressionLine() { return regressionLine; } /** Set the regression line for this legend entry. * @param regressionLine a LinearXYPlotLine */ public void setRegressionLine(LinearXYPlotLine regressionLine) { this.regressionLine = regressionLine; if (regressionLine != null) regressionLine.setForeground(foregroundColor); } /** * @return the dataSetName */ public String getDataSetName() { return dataSetName; } /** * @param dataSetName the dataSetName to set */ public void setDataSetName(String dataSetName) { this.dataSetName = dataSetName; } @Override public void attachPlotLine(AbstractPlotLine plotLine) { // TODO Auto-generated method stub } @Override public String getDisplayedName() { return this.baseDisplayName; } }