/******************************************************************************* * 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.atlasStyler.swing; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Dimension; import java.awt.Image; import java.awt.event.ActionEvent; import java.awt.image.BufferedImage; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.Vector; import javax.swing.AbstractAction; import javax.swing.BorderFactory; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.ListSelectionModel; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import javax.swing.table.DefaultTableModel; import javax.swing.table.TableCellRenderer; import net.miginfocom.swing.MigLayout; import org.apache.log4j.Logger; import org.geopublishing.atlasStyler.ASUtil; import org.geopublishing.atlasStyler.AtlasStylerVector; import org.geopublishing.atlasStyler.RuleChangeListener; import org.geopublishing.atlasStyler.RuleChangedEvent; import org.geopublishing.atlasStyler.rulesLists.SingleRuleList; import org.geopublishing.atlasViewer.swing.Icons; import org.geotools.factory.CommonFactoryFinder; import org.geotools.styling.Graphic; import org.geotools.styling.PointSymbolizer; import org.geotools.styling.PolygonSymbolizer; import org.geotools.styling.Rule; import org.geotools.styling.Symbolizer; import org.opengis.feature.type.GeometryDescriptor; import com.vividsolutions.jts.geom.Point; import de.schmitzm.geotools.LegendIconFeatureRenderer; import de.schmitzm.geotools.feature.FeatureUtil; import de.schmitzm.geotools.feature.FeatureUtil.GeometryForm; import de.schmitzm.geotools.styling.chartsymbols.ChartGraphic; import de.schmitzm.lang.LangUtil; import de.schmitzm.swing.CancellableDialogAdapter; import de.schmitzm.swing.JPanel; import de.schmitzm.swing.SmallButton; import de.schmitzm.swing.SwingUtil; public class SymbolEditorGUI extends CancellableDialogAdapter { protected Logger LOGGER = LangUtil.createLogger(this); protected static final String PROPERTY_LAYER_SELECTION = "LAYER_SELECTION"; protected static final String PROPERTY_SYMBOL_CHANGED = "SYMBOL_CHANGED"; protected static final String PROPERTY_LAYERS_CHANGED = "LAYERS_CHANGED"; private static final String DIALOG_TITLE = ASUtil.R("SymbolEditor.Title"); private static final Dimension SYMBOL_SIZE = new Dimension(60, 60); private JPanel jContentPane = null; private JPanel jPanelPreview = null; private JPanel jPanelProperties = null; private JTable jTableLayers = null; private JButton jButtonNewLayer = null; private JButton jButtonRemoveLayer = null; private JButton jButtonLayerDown = null; private JButton jButtonLayerUp = null; private JLabel jLabelPreviewIcon = null; private final SingleRuleList<Symbolizer> singleSymbolRuleList; private DefaultTableModel tableModelLayers; protected Symbolizer selectedSymbolizer; protected SingleRuleList<?> backup; @Override public void cancel() { backup.copyTo(singleSymbolRuleList); } /** * This little {@link PropertyChangeListener} propagates changes in the * properties JPanel to the {@link SymbolEditorGUI} and to any * {@link RuleChangeListener} */ protected PropertyChangeListener propagateL = new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { if (!evt.getPropertyName().equals( AbstractStyleEditGUI.PROPERTY_UPDATED)) return; SymbolEditorGUI.this.firePropertyChange(PROPERTY_SYMBOL_CHANGED, null, null); singleSymbolRuleList.fireEvents(new RuleChangedEvent( "SymbolEditorGUI propagator", singleSymbolRuleList)); // LOGGER.debug("Propagating..."); } }; /** * @param selectorGUI */ public SymbolEditorGUI(Component owner, AtlasStylerVector asv, SingleRuleList<? extends Symbolizer> singleSymbolRuleList) { super(owner, DIALOG_TITLE); if (asv == null) throw new IllegalStateException("asv may not be null here!"); this.asv = asv; this.singleSymbolRuleList = (SingleRuleList<Symbolizer>) singleSymbolRuleList; backup = singleSymbolRuleList.clone(false); initialize(); } /** * This method initializes this * * @return void */ private void initialize() { this.setContentPane(getJContentPane()); this.pack(); SwingUtil.setRelativeFramePosition(this, getParent(), SwingUtil.BOUNDS_OUTER, SwingUtil.NORTHEAST); } /** * This method initializes jContentPane * * @return javax.swing.JPanel */ private JPanel getJContentPane() { if (jContentPane == null) { jContentPane = new JPanel(new BorderLayout()); JPanel upper = new JPanel(new MigLayout("flowy")); // Has to initialized before getJPanelPreview final JPanel jPanelProperties2 = getJPanelProperties(); upper.add(getJPanelPreview(), "center, split 2, growx"); upper.add(getJPanelLayers(), "wrap"); upper.add(jPanelProperties2, "span 2, top"); jContentPane.add(upper, BorderLayout.CENTER); jContentPane.add(getJPanelButtons(), BorderLayout.SOUTH); } return jContentPane; } /** * Return a {@link BufferedImage} that represent the symbol. * * @param bg * Paints the background of the {@link Image} in that color or * not. */ public BufferedImage getSymbolImage(Symbolizer symb, Color bg) { // TODO Caching? // Dynamic ChartGraphics have to be fixed first so that they use static // values if (ChartGraphic.isChart(symb)) symb = ChartGraphic.getFixDataSymbolizer(symb); final Rule rule = CommonFactoryFinder.getStyleFactory(null) .createRule(); rule.symbolizers().add(symb); final LegendIconFeatureRenderer renderer = LegendIconFeatureRenderer .getInstance(); BufferedImage image; try { image = renderer .createImageForRule(rule, FeatureUtil.createFeatureType(Point.class), SYMBOL_SIZE, bg); } catch (Exception e) { LOGGER.warn(e.getMessage(), e); image = new BufferedImage(SYMBOL_SIZE.width, SYMBOL_SIZE.height, BufferedImage.TYPE_INT_ARGB); } return image; } final PropertyChangeListener listenerUpdatePreview = new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { if ((evt.getPropertyName().equals(PROPERTY_SYMBOL_CHANGED)) || (evt.getPropertyName().equals(PROPERTY_LAYERS_CHANGED))) { jLabelPreviewIcon.setIcon(new ImageIcon(singleSymbolRuleList .getImage(new Dimension(SYMBOL_SIZE.width, SYMBOL_SIZE.height)))); // Do not! fire a new event! The preview is updated. That is the // sense of the listener // singleSymbolRuleList.fireEvents(new RuleChangedEvent( // "Updating Preview in SymbolEditorGUI", // singleSymbolRuleList)); } } }; /** * This Panel shows one previerw icon, which is the sum of all layers * (Symbolizers) listed in {@link #getJPanelLayers()} */ private JPanel getJPanelPreview() { if (jPanelPreview == null) { jLabelPreviewIcon = new JLabel(new ImageIcon( singleSymbolRuleList.getImage(new Dimension( SYMBOL_SIZE.width, SYMBOL_SIZE.height)))); SymbolEditorGUI.this .addPropertyChangeListener(listenerUpdatePreview); jPanelPreview = new JPanel(new MigLayout(), ASUtil.R("SymbolSelector.Preview.BorderTitle")); jPanelPreview.add(jLabelPreviewIcon, "center"); } return jPanelPreview; } /** * This panel shows the layers (Sybolizers) that this Symbol consists of. * The composite of these layers (Symbolizers) is previewd in * {@link #getJPanelPreview()} */ private JPanel getJPanelLayers() { JPanel jPanelLayers; jPanelLayers = new JPanel(new MigLayout(), ASUtil.R("SymbolEditor.SymbolLayers")); jPanelLayers.add(new JScrollPane(getJTableLayers()), "wrap, top, w " + (SYMBOL_SIZE.width + 30) + ", h " + (SYMBOL_SIZE.height * 3)); // ,"top, wrap, growy, shrinkx" // jPanelLayers.add(new JScrollPane(getJTableLayers()),"wrap"); // //,"top, wrap, growy, shrinkx" jPanelLayers.add(getJButtonNewLayer(), "split 4"); jPanelLayers.add(getJButtonRemoveLayer()); jPanelLayers.add(getJButtonLayerDown()); jPanelLayers.add(getJButtonLayerUp()); return jPanelLayers; } private Symbolizer guiIsUpToDateForThisSymbolizer = null; final private AtlasStylerVector asv; /** * This method initializes jPanel2 * * @return javax.swing.JPanel */ private JPanel getJPanelProperties() { if (jPanelProperties == null) { jPanelProperties = new JPanel(new BorderLayout()); jPanelProperties.setBorder(BorderFactory.createTitledBorder(ASUtil .R("SymbolEditor.Properties"))); addPropertyChangeListener(new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { if ((evt.getPropertyName().equals(PROPERTY_LAYER_SELECTION)) // || (evt.getPropertyName() // .equals(PROPERTY_LAYERS_CHANGED)) ) { if (selectedSymbolizer == null) { jPanelProperties.removeAll(); jPanelProperties.add( new JLabel(ASUtil .R("SymbolEditor.SelectALayer")), BorderLayout.CENTER); } else { // Check, that the selected symbolizer really // changed.. otherwise we are updating the GUI too // often if (selectedSymbolizer == guiIsUpToDateForThisSymbolizer) return; GeometryDescriptor ruleGeometry = singleSymbolRuleList .getGeometryDescriptor(); // JTable tableLayers = getJTableLayers(); // int selectedRow = tableLayers.getSelectedRow(); JComponent symbolEditGUI = null; switch (FeatureUtil.getGeometryForm(ruleGeometry)) { case POINT: Graphic graphic = ((PointSymbolizer) selectedSymbolizer) .getGraphic(); symbolEditGUI = new GraphicEditGUI(asv, graphic, GeometryForm.POINT); break; case LINE: symbolEditGUI = new LineSymbolEditGUI( asv, ((org.geotools.styling.LineSymbolizer) selectedSymbolizer)); break; case POLYGON: symbolEditGUI = new PolygonSymbolEditGUI( asv, ((PolygonSymbolizer) selectedSymbolizer)); break; default: throw new IllegalStateException( "unrecognized type"); } symbolEditGUI.addPropertyChangeListener(propagateL); // LOGGER.debug("Adding propagateL to " // + symbolEditGUI); /** * Update the jPanelProperties (if we have a GUI) */ jPanelProperties.removeAll(); if (symbolEditGUI != null) { jPanelProperties.add(symbolEditGUI, BorderLayout.NORTH); } } // Re-Layout the GUI pack(); jPanelProperties.repaint(); guiIsUpToDateForThisSymbolizer = selectedSymbolizer; } } }); } return jPanelProperties; } /** * This method initializes jTable * * @return javax.swing.JTable */ private JTable getJTableLayers() { if (jTableLayers == null) { jTableLayers = new JTable(getLayersTableModel()); jTableLayers.setRowHeight(SYMBOL_SIZE.height + 2); jTableLayers.getColumnModel().getColumn(0).setMaxWidth(20); jTableLayers.getColumnModel().getColumn(1) .setMaxWidth(SYMBOL_SIZE.width + 2); jTableLayers.setDefaultRenderer(BufferedImage.class, new TableCellRenderer() { @Override public Component getTableCellRendererComponent( JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { if (value != null) return new JLabel(new ImageIcon( (BufferedImage) value)); else return new JLabel("add a layer to your symbol"); // i8n // TODO// Default// Image // that // tells // the // user // to } }); // Listens for removed or new table entries (= PointSymbolizers) addPropertyChangeListener(new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { if (evt.getPropertyName().equals(PROPERTY_LAYERS_CHANGED)) { int selectedRow = getJTableLayers().getSelectedRow(); getLayersTableModel().fireTableDataChanged(); getJTableLayers().getSelectionModel() .addSelectionInterval(selectedRow, selectedRow); } else if ((evt.getPropertyName() .equals(PROPERTY_SYMBOL_CHANGED))) { for (int i = 0; i < getLayersTableModel().getRowCount(); i++) { getLayersTableModel().fireTableCellUpdated(i, 1); } } } }); jTableLayers.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); jTableLayers.getSelectionModel().addListSelectionListener( new ListSelectionListener() { @Override public void valueChanged(ListSelectionEvent e) { if (e.getValueIsAdjusting()) return; int idx = jTableLayers.getSelectedRow(); if (idx == -1) { selectedSymbolizer = null; } else { selectedSymbolizer = singleSymbolRuleList .getSymbolizers().get(idx); } SymbolEditorGUI.this.firePropertyChange( PROPERTY_LAYER_SELECTION, null, null); } }); if (getLayersTableModel().getRowCount() > 0) { jTableLayers.getSelectionModel().addSelectionInterval(0, 0); } } return jTableLayers; } protected DefaultTableModel getLayersTableModel() { if (tableModelLayers == null) { tableModelLayers = new DefaultTableModel() { @Override public Class<?> getColumnClass(int columnIndex) { if (columnIndex == 0) return String.class; if (columnIndex == 1) return ImageIcon.class; return null; } @Override public int getColumnCount() { return 2; } @Override public String getColumnName(int columnIndex) { if (columnIndex == 0) return AtlasStylerVector .R("SymbolEditor.TableColumns.Order"); if (columnIndex == 1) return AtlasStylerVector .R("SymbolEditor.TableColumns.LayerPreview"); return null; } @Override public int getRowCount() { return singleSymbolRuleList.getSymbolizers().size(); } @Override public Object getValueAt(int rowIndex, int columnIndex) { if (columnIndex == 0) return rowIndex + 1; if (columnIndex == 1) { Vector<? extends Symbolizer> symbolizers = singleSymbolRuleList .getSymbolizers(); ImageIcon imageIcon = new ImageIcon(getSymbolImage( symbolizers.get(rowIndex), jTableLayers.getBackground())); return imageIcon; } return null; } @Override public boolean isCellEditable(int row, int column) { return false; } }; } return tableModelLayers; } /** * This method initializes jButton * * @return javax.swing.JButton */ private JButton getJButtonNewLayer() { if (jButtonNewLayer == null) { jButtonNewLayer = new SmallButton(new AbstractAction(" + ") { @Override public void actionPerformed(ActionEvent e) { singleSymbolRuleList.addNewDefaultLayer(); // Select the newly created layer jTableLayers.getSelectionModel().addSelectionInterval(0, singleSymbolRuleList.getSymbolizers().size() - 1); SymbolEditorGUI.this.firePropertyChange( PROPERTY_LAYERS_CHANGED, null, null); } }); jButtonNewLayer.setToolTipText(ASUtil .R("SymbolEditor.Action.AddSymbolLayer.TT")); } return jButtonNewLayer; } /** * This method initializes a button to remove the selected layer. * * @return javax.swing.JButton */ private JButton getJButtonRemoveLayer() { if (jButtonRemoveLayer == null) { jButtonRemoveLayer = new SmallButton(new AbstractAction(" - ") { @Override public void actionPerformed(ActionEvent e) { if (jTableLayers.getSelectedRow() >= 0) { // Something is selected singleSymbolRuleList.removeSymbolizer(jTableLayers .getSelectedRow()); jTableLayers.getSelectionModel().clearSelection(); jButtonLayerUp.setEnabled(false); jButtonLayerDown.setEnabled(false); jButtonRemoveLayer.setEnabled(false); selectedSymbolizer = null; // // // Clear the cache of all GUIs, as they are chached // with // // their RowIndex as key (Sadly a PointSymbolizer // // doesn't have a name or title) // // editPropertiesGUICache.clear(); if (getLayersTableModel().getRowCount() > 0) { jTableLayers.getSelectionModel() .addSelectionInterval(0, 0); } SymbolEditorGUI.this.firePropertyChange( PROPERTY_LAYERS_CHANGED, null, null); } } }); jTableLayers.getSelectionModel().addListSelectionListener( new ListSelectionListener() { @Override public void valueChanged(ListSelectionEvent e) { if (e.getValueIsAdjusting()) return; if (((ListSelectionModel) e.getSource()) .getMinSelectionIndex() < 0) { jButtonRemoveLayer.setEnabled(false); } else { jButtonRemoveLayer.setEnabled(true); } } }); jButtonRemoveLayer.setEnabled(false); jButtonRemoveLayer.setToolTipText(ASUtil .R("SymbolEditor.Action.RemoveSymbolLayer.TT")); } return jButtonRemoveLayer; } /** * This method initializes jButton * * @return javax.swing.JButton */ private JButton getJButtonLayerDown() { if (jButtonLayerDown == null) { jButtonLayerDown = new SmallButton(new AbstractAction("", Icons.getDownArrowIcon()) { @Override public void actionPerformed(ActionEvent e) { if (jTableLayers.getSelectedRow() >= 0 && jTableLayers.getSelectedRow() < jTableLayers .getModel().getRowCount() - 1) { final int selectedRow = jTableLayers.getSelectedRow(); singleSymbolRuleList.move(selectedRow, 1); jTableLayers.getSelectionModel().clearSelection(); jTableLayers.getSelectionModel().addSelectionInterval( selectedRow + 1, selectedRow + 1); SymbolEditorGUI.this.firePropertyChange( PROPERTY_LAYERS_CHANGED, null, null); } } }); jTableLayers.getSelectionModel().addListSelectionListener( new ListSelectionListener() { @Override public void valueChanged(ListSelectionEvent e) { if (e.getValueIsAdjusting()) return; ListSelectionModel lsm = ((ListSelectionModel) e .getSource()); if ((lsm.getMinSelectionIndex() < 0) || (lsm.getMinSelectionIndex() >= jTableLayers .getModel().getRowCount() - 1)) { jButtonLayerDown.setEnabled(false); } else { jButtonLayerDown.setEnabled(true); } } }); jButtonLayerDown.setEnabled(false); jButtonLayerDown.setToolTipText(ASUtil .R("SymbolEditor.Action.MoveUpSymbolLayerDown.TT")); } return jButtonLayerDown; } /** * This method initializes jButton * * @return javax.swing.JButton */ private JButton getJButtonLayerUp() { if (jButtonLayerUp == null) { jButtonLayerUp = new SmallButton(new AbstractAction("", Icons.getUpArrowIcon()) { @Override public void actionPerformed(ActionEvent e) { if (jTableLayers.getSelectedRow() > 0) { final int selectedRow = jTableLayers.getSelectedRow(); singleSymbolRuleList.move(selectedRow, -1); jTableLayers.getSelectionModel().clearSelection(); jTableLayers.getSelectionModel().addSelectionInterval( selectedRow - 1, selectedRow - 1); SymbolEditorGUI.this.firePropertyChange( PROPERTY_LAYERS_CHANGED, null, null); // // Something is selected // List<Symbolizer> symbolizers = singleSymbolRuleList // .getSymbolizers(); // // // if (jTableLayers.getSelectedRow() > 0) { // Symbolizer symbolizer = symbolizers // .remove(jTableLayers.getSelectedRow()); // symbolizers.insertElementAt(symbolizer, // jTableLayers.getSelectedRow() - 1); // jTableLayers.getSelectionModel() // .addSelectionInterval(0, // jTableLayers.getSelectedRow() - 1); // // // // // Clear the cache of all GUIs, as they are // // // chached with // // // their RowIndex as key (Sadly a // // // PointSymbolizer // // // doesn't have a name or title) // // editPropertiesGUICache.clear(); // // SymbolEditorGUI.this.firePropertyChange( // PROPERTY_LAYERS_CHANGED, null, null); // } } } }); jTableLayers.getSelectionModel().addListSelectionListener( new ListSelectionListener() { @Override public void valueChanged(ListSelectionEvent e) { if (e.getValueIsAdjusting()) return; if (((ListSelectionModel) e.getSource()) .getMinSelectionIndex() < 1) { jButtonLayerUp.setEnabled(false); } else { jButtonLayerUp.setEnabled(true); } } }); jButtonLayerUp.setEnabled(false); jButtonLayerUp.setToolTipText(ASUtil .R("SymbolEditor.Action.MoveUpSymbolLayerUp.TT")); } return jButtonLayerUp; } @Override public void dispose() { if (isDisposed) return; // Remove the listener that updated the preview and sends the events on // to the next layer LOGGER.debug("Removing the updatePreviewPanelIconOnChange Listener"); removePropertyChangeListener(listenerUpdatePreview); // // if (editPropertiesGUICache != null) { // editPropertiesGUICache.clear(); // } super.dispose(); } /** * This method initializes jPanel1 * * @return javax.swing.JPanel */ private JPanel getJPanelButtons() { JPanel jPanelButtons = new JPanel(new MigLayout("", "")); jPanelButtons.add(getOkButton(), "tag ok"); jPanelButtons.add(getCancelButton(), "tag cancel"); return jPanelButtons; } }