/******************************************************************************* * Copyright (c) 2010 Stefan A. Tzeggai. * All rights reserved. This program and the accompanying materials * are made available under the terms of the GNU Lesser Public License v2.1 * which accompanies this distribution, and is available at * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html * * Contributors: * Stefan A. Tzeggai - initial API and implementation ******************************************************************************/ package org.geopublishing.atlasViewer.swing; import java.awt.BorderLayout; import java.awt.Component; import java.awt.Font; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.util.ArrayList; import java.util.List; import java.util.Vector; import javax.swing.BorderFactory; import javax.swing.JComboBox; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JTabbedPane; import javax.swing.JTextArea; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import org.apache.log4j.Logger; import org.geopublishing.atlasViewer.GpCoreUtil; import org.geopublishing.atlasViewer.dp.layer.DpLayer; import org.geopublishing.atlasViewer.dp.layer.LayerStyle; import org.geopublishing.atlasViewer.map.Map; import org.geotools.map.MapLayer; import org.geotools.styling.FeatureTypeStyle; import org.geotools.styling.Style; import de.schmitzm.geotools.styling.StyledFeaturesInterface; import de.schmitzm.geotools.styling.StyledLayerInterface; import de.schmitzm.geotools.styling.StyledLayerUtil; import de.schmitzm.geotools.styling.StyledRasterInterface; import de.schmitzm.geotools.styling.StylingUtil; import de.schmitzm.i18n.I18NUtil; import de.schmitzm.i18n.Translation; import de.schmitzm.jfree.chart.style.ChartStyle; import de.schmitzm.lang.LangUtil; import de.schmitzm.swing.JPanel; /** * This helper class is helping to create nice legend images, {@link JComponent} * s etc. */ public class SldLegendUtil { static final private Logger LOGGER = LangUtil .createLogger(SldLegendUtil.class); /** Determine height of the icons depending on the Font used **/ protected static final Font FONT = new JLabel().getFont(); /** The hight of the single legend icons **/ public final static int ICONHEIGHT = new JLabel().getFontMetrics(FONT) .getHeight(); /** The width of the single legend icons **/ public final static int ICONWIDTH = 23; /** The size of the gaps between the color-symbols in raster legends * */ public final static int gapHeight = 3; /** * Creates a legend using the given description and {@link Style}. * * @param style * All selection related {@link FeatureTypeStyle}s are * automatically removed. * @param desc * may be <code>null</code>. In that case no description is used. * @param scaleDenominator * <code>null</code> of a Scale Denominator to determine whether * a Rule is actually visible in that scale. */ static public JPanel createLegend( final StyledLayerInterface<?> styledLayer, Style style, String desc, Double scaleDenominator) { if (styledLayer == null) throw new IllegalArgumentException("styledLayer may not be null!"); if (style == null) { style = styledLayer.getStyle(); } // Remove any selection & text if (style != null) style = StylingUtil.removeSelectionFeatureTypeStyle(style); JPanel oneStyleLegendBox; if (styledLayer instanceof StyledFeaturesInterface) { StyledFeaturesInterface styledFeaturesInterface = (StyledFeaturesInterface) styledLayer; oneStyleLegendBox = StyledLayerUtil.createLegendSwingPanel(style, styledFeaturesInterface.getSchema(), ICONWIDTH, ICONHEIGHT, scaleDenominator, styledFeaturesInterface.getAttributeMetaDataMap()); } else if (styledLayer instanceof StyledRasterInterface<?>) { oneStyleLegendBox = StyledLayerUtil.createLegendSwingPanel( (StyledRasterInterface<?>) styledLayer, style, ICONWIDTH, ICONHEIGHT, scaleDenominator); } else { throw new IllegalArgumentException("Can't create a legend for " + styledLayer.getClass().getSimpleName()); } if (desc != null && !desc.isEmpty()) { JPanel descAndLegend = new JPanel(new BorderLayout()); descAndLegend.setBorder(BorderFactory.createEmptyBorder()); // We have description. Lets use it as a label above the legend JTextArea descLabel = new JTextArea(desc); descLabel.setWrapStyleWord(true); descLabel.setEditable(false); descLabel.setLineWrap(true); descLabel.setOpaque(false); descAndLegend.add(descLabel, BorderLayout.NORTH); descAndLegend.add(oneStyleLegendBox, BorderLayout.CENTER); return descAndLegend; } else return oneStyleLegendBox; } /** * Creates a legend using the given translated description and and * {@link Style}. * * @param scaleDenominator * <code>null</code> of a Scale Denominator to determine whether * a Rule is actually visible in that scale. * * @param layerStyle * If <code>null</code>, description and {@link Style} from the * {@link StyledLayerInterface} are used. */ static public JPanel createLegend( final StyledLayerInterface<?> styledLayer, Style style, Translation desc, Double scaleDenominator) { return createLegend(styledLayer, style, desc != null ? desc.toString() : null, scaleDenominator); } /** * Creates a legend using any description (if available) and {@link Style} * from the {@link StyledLayerInterface}. * @param scaleDenominator * <code>null</code> of a Scale Denominator to determine whether a Rule is * actually visible in that scale. */ static public JPanel createLegend( final StyledLayerInterface<?> styledLayer, Double scaleDenominator) { return createLegend(styledLayer, styledLayer.getStyle(), styledLayer.getDesc(), scaleDenominator); } /** * Creates a legend using the description and {@link Style} from the * {@link LayerStyle}. * * @param layerStyle * If <code>null</code>, description and {@link Style} from the * {@link StyledLayerInterface} are used. */ static public JComponent createLegend( final StyledLayerInterface<?> styledLayer, LayerStyle layerStyle, Double scaleDenominator) { if (layerStyle == null) return createLegend(styledLayer, scaleDenominator); Translation desc = null; if (!I18NUtil.isEmpty(layerStyle.getDesc())) { desc = layerStyle.getDesc(); } else if (!I18NUtil.isEmpty(styledLayer.getDesc())) { desc = styledLayer.getDesc(); } return createLegend(styledLayer, layerStyle.getStyle(), desc, scaleDenominator); } /** * @param mapLayer * The GeoTools {@link MapLayer} that will be affected by style * changes. * @param availStyleIDs * A {@link List} of IDs of AdditionalStyles * @param dpLayer * The {@link DpLayer} that holds all the additional styles * * @return a new {@link Component} that will represent all available Styles * for a {@link DpLayer}, if there are less than 5 additional * styles. If there are 5 or more additional styles, a * {@link JComboBox} is returned. If only one additonal style is * available, it will replace the {@link DpLayer} as the source for * title and description. */ public static Component createAdditionalStylesPane(final MapLayer mapLayer, final ArrayList<String> availStyleIDs, final DpLayer<?, ? extends ChartStyle> dpLayer, final Map map, final AtlasMapLegend atlasMapLegend, Double scaleDenominator) { /** * Determine whether the the number of rules differs so much, that a * JTabbedPne would look ugly */ int min = Integer.MAX_VALUE; int max = Integer.MIN_VALUE; for (String lsID : availStyleIDs) { LayerStyle ls = dpLayer.getLayerStyleByID(lsID); List<FeatureTypeStyle> featureTypeStyles = ls.getStyle() .featureTypeStyles(); if (featureTypeStyles.size() > 0) { int length = featureTypeStyles.get(0).rules().size(); min = Math.min(min, length); max = Math.max(max, length); } } if (availStyleIDs.size() > 2 || (max - min > 4)) { String selectedStyleID = map.getSelectedStyleID(dpLayer.getId()); if (selectedStyleID == null) { // We might just have created new additional styles, and none // has been selected yet. But thats not a reason for a NPE - // right? Just use the first one. selectedStyleID = availStyleIDs.get(0); } // Let's create a JComboBox final JComboBoxForAddStylesPanel comboBoxForAddStylesPanel = new JComboBoxForAddStylesPanel( availStyleIDs, dpLayer, mapLayer, selectedStyleID, atlasMapLegend, scaleDenominator); comboBoxForAddStylesPanel.getComboBox().addItemListener( new ItemListener() { @Override public void itemStateChanged(ItemEvent e) { if (e.getStateChange() == ItemEvent.DESELECTED) return; /** * Remember the selection as the default style to * use when opening this map. */ if (comboBoxForAddStylesPanel.getComboBox() .getSelectedIndex() < 0) { map.setSelectedStyleID(dpLayer.getId(), null); } else { final String styleID = availStyleIDs .get(comboBoxForAddStylesPanel .getComboBox() .getSelectedIndex()); map.setSelectedStyleID(dpLayer.getId(), styleID); } atlasMapLegend.recreateLayerList(dpLayer.getId()); } }); return comboBoxForAddStylesPanel; } else { // Let's create a JTabbedPane final JTabbedPane legendPanel = new JTabbedPane(); /** * Now fill the tabs... */ int count = 0; for (String lsID : availStyleIDs) { LayerStyle ls = dpLayer.getLayerStyleByID(lsID); JComponent oneStyleLegend = SldLegendUtil.createLegend(dpLayer, ls, scaleDenominator); legendPanel.addTab(ls.getTitle() != null ? ls.getTitle() .toString() : "UNNAMED!", oneStyleLegend); /** * One of these Tabs should be selected: */ if (lsID.equals(map.getSelectedStyleID(dpLayer.getId()))) { legendPanel.setSelectedComponent(oneStyleLegend); } legendPanel.setToolTipTextAt(count, GpCoreUtil.R( "Legend.Views.TabbedPane.ToolTip", ls.getTitle() .toString())); count++; } /** * This listener reacts to the tab selections. It may only be added * AFTER the tabs have been created. */ if (availStyleIDs.size() > 1) legendPanel.addChangeListener(new ChangeListener() { @Override public void stateChanged(ChangeEvent e) { /** * The selected Style is applied to the maplayer */ int selectedIndex = legendPanel.getSelectedIndex(); if (selectedIndex >= 0) { String styleID = availStyleIDs.get(selectedIndex); LayerStyle layerStyle = dpLayer .getLayerStyleByID(styleID); mapLayer.setStyle(layerStyle.getStyle()); map.setSelectedStyleID(dpLayer.getId(), styleID); } } }); if (availStyleIDs.size() == 1) { return legendPanel.getComponent(0); } else { return legendPanel; } } } public static class JComboBoxForAddStylesPanel extends JPanel { protected Logger LOGGER = LangUtil .createLogger(JComboBoxForAddStylesPanel.class); /** The actual {@link JComboBox} **/ private final JComboBox comboBox; /** * @param availStyleIDs * a list of additional style IDs to offer * @param dpLayer * {@link DpLayer} where the add. styles come from * @param mapLayer * the {@link MapLayer} where the styles will be set when * changed * @param selectedStyleId * The ID if the LayerStyle that should be selected by * default * @param scaleDenominator */ public JComboBoxForAddStylesPanel( final ArrayList<String> availStyleIDs, final DpLayer dpLayer, final MapLayer mapLayer, String selectedStyleId, final AtlasMapLegend atlasMapLegend, Double scaleDenominator) { super(new BorderLayout()); int selectedIndex = -1; final Vector<String> styleTitles = new Vector<String>(); for (int i = 0; i < availStyleIDs.size(); i++) { LayerStyle ls = dpLayer.getLayerStyleByID(availStyleIDs.get(i)); if (ls == null) { LOGGER.warn("JComboBoxForAddStylesPanel has been told to offer a view with ID=" + availStyleIDs.get(i) + ", but it doesn't exist. We omit the style."); continue; } styleTitles.add(ls.getTitle().toString()); if (availStyleIDs.get(i).equals(selectedStyleId)) { // This style is selected. We set it as the selected layer // in the ComboBox later. selectedIndex = i; } } comboBox = new JComboBox(styleTitles); comboBox.setSelectedIndex(selectedIndex); // Utilities.addMouseWheelForCombobox(getComboBox()); // // /** // * This Listener reacts to selections on the ComboBox with A) // * updating the legnd, and B) updting the map's style. // */ // getComboBox().addItemListener(new ItemListener() { // // @Override // public void itemStateChanged(ItemEvent e) { // if (e.getStateChange() == ItemEvent.DESELECTED) { // return; // } // // String newSelectedLayerStyle = availStyleIDs.get(getComboBox() // .getSelectedIndex()); // // map.set // // // final LayerStyle ls = dpLayer // // .getLayerStyleByID(newSelectedLayerStyle); // // /** // // * Because JComboBox doesn't have all the legends as // // * components on the tabs, we create them // // */ // // { // // JComboBoxForAddStylesPanel.this.removeAll(); // // JComboBoxForAddStylesPanel.this.add(comboBox, // // BorderLayout.NORTH); // // final JComponent legend = LegendHelper.createLegend( // // dpLayer, ls); // // JComboBoxForAddStylesPanel.this.add(legend, // // BorderLayout.CENTER); // // } // atlasMapLegend.recreateLayerList(dpLayer.getId()); // // // mapLayer.setStyle(ls.getStyle()); // } // // }); add(comboBox, BorderLayout.NORTH); add(SldLegendUtil.createLegend(dpLayer, dpLayer.getLayerStyleByID(selectedStyleId), scaleDenominator), BorderLayout.CENTER); } public JComboBox getComboBox() { return comboBox; } } }