/* * The Unified Mapping Platform (JUMP) is an extensible, interactive GUI * for visualizing and manipulating spatial features with geometry and attributes. * * Copyright (C) 2003 Vivid Solutions * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * For more information, contact: * * Vivid Solutions * Suite #1A * 2328 Government Street * Victoria BC V8T 5G5 * Canada * * (250)385-6040 * www.vividsolutions.com */ package com.vividsolutions.jump.workbench.ui.renderer.style; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; 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.MouseAdapter; import java.awt.event.MouseEvent; import java.util.Collection; import java.util.EventObject; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.SortedMap; import java.util.SortedSet; import java.util.TreeMap; import java.util.TreeSet; import java.util.Vector; import javax.swing.BorderFactory; import javax.swing.DefaultCellEditor; import javax.swing.DefaultComboBoxModel; import javax.swing.Icon; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComboBox; import javax.swing.JComponent; import javax.swing.JDialog; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JSlider; import javax.swing.JTable; import javax.swing.ScrollPaneConstants; import javax.swing.SwingUtilities; import javax.swing.event.CellEditorListener; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import javax.swing.event.TableModelEvent; import javax.swing.event.TableModelListener; import javax.swing.table.DefaultTableCellRenderer; import javax.swing.table.TableCellEditor; import javax.swing.table.TableCellRenderer; import com.vividsolutions.jump.I18N; import com.vividsolutions.jump.feature.AttributeType; import com.vividsolutions.jump.feature.Feature; import com.vividsolutions.jump.feature.FeatureSchema; import com.vividsolutions.jump.util.CollectionUtil; import com.vividsolutions.jump.util.Range; import com.vividsolutions.jump.workbench.WorkbenchContext; import com.vividsolutions.jump.workbench.model.Layer; import com.vividsolutions.jump.workbench.model.LayerManager; import com.vividsolutions.jump.workbench.plugin.AbstractPlugIn; import com.vividsolutions.jump.workbench.plugin.EnableCheck; import com.vividsolutions.jump.workbench.plugin.MultiEnableCheck; import com.vividsolutions.jump.workbench.plugin.PlugInContext; import com.vividsolutions.jump.workbench.ui.ColorPanel; import com.vividsolutions.jump.workbench.ui.EnableableToolBar; import com.vividsolutions.jump.workbench.ui.GUIUtil; import com.vividsolutions.jump.workbench.ui.OKCancelPanel; import com.vividsolutions.jump.workbench.ui.images.IconLoader; import com.vividsolutions.jump.workbench.ui.renderer.style.attributeclassifications.JenksBreaksColorThemingState; import com.vividsolutions.jump.workbench.ui.renderer.style.attributeclassifications.MaximalBreaksColorThemingState; import com.vividsolutions.jump.workbench.ui.renderer.style.attributeclassifications.MeanSTDevColorThemingState; import com.vividsolutions.jump.workbench.ui.renderer.style.attributeclassifications.QuantileColorThemingState; import com.vividsolutions.jump.workbench.ui.renderer.style.attributeclassifications.RangeColorThemingState; import com.vividsolutions.jump.workbench.ui.style.BasicStylePanel; import com.vividsolutions.jump.workbench.ui.style.StylePanel; /** * A panel to choose Color Theming options. * The panel includes * <ul> * <li>a checkbox to enable Color Theming</li> * <li>a combobox to choose Attribute</li> * <li>a combobox to choose Classification Method plus additional parameters * in case of range-based classification</li> * <li>a combobox to choose a Color Scheme</li> * <li>an table to display colors with two plugins to add/remove lines</li> * <li>a slider to set the transparency</li> * </ul> * * @author </p>jonathan aquino (original author) * @author </p>obedel and ebocher (2005 - added Quantile Color Theming) * @author </p>ssteiniger (2009 - added 3 more range-based classification methods) * @author </p>mmichaud (2011 - debug, remove deprecated code and comments, give * priority to attribute value choice instead of classification choice) */ public class ColorThemingStylePanel extends JPanel implements StylePanel { private static final String CUSTOM_ENTRY = I18N.get("ui.renderer.style.ColorThemingPanel.custom"); public static final String TITLE = I18N.get("ui.renderer.style.ColorThemingPanel.colour-theming"); public static final String COLOR_SCHEME_KEY = ColorThemingStylePanel.class.getName() + " - COLOR SCHEME"; private WorkbenchContext workbenchContext; private Layer layer; // jPanel1 contains enable, attribute, classification and color scheme options private JPanel jPanel1 = new JPanel(); private JCheckBox enableColorThemingCheckBox = new JCheckBox(); private JLabel attributeLabel = new JLabel(); private JComboBox attributeNameComboBox = new JComboBox(); private String lastAttributeName; private JLabel classificationLabel = new JLabel(I18N.get("ui.renderer.style.ColorThemingStylePanel.Classification-Method")); private JComboBox classificationComboBox = new JComboBox(); private JLabel colorSchemeLabel = new JLabel(); private JComboBox colorSchemeComboBox = new JComboBox(); // jPanel2 contains the scrollPane (table, add/remove plugin and transparency slider) private JPanel jPanel2 = new JPanel(); private JScrollPane scrollPane = new JScrollPane(); private DefaultTableCellRenderer allOtherValuesRenderer = new DefaultTableCellRenderer(); // main table displaying color theming private JTable table = new JTable() { public TableCellRenderer getCellRenderer(int row, int column) { TableCellRenderer renderer = getCellRendererProper(row, column); if (renderer instanceof JComponent) { updateBackground((JComponent) renderer); } return renderer; } private TableCellRenderer getCellRendererProper(int row, int column) { if ((row == 0) && (column == attributeColumn())) { return allOtherValuesRenderer; } if ((row == 0) && (column == labelColumn())) { return allOtherValuesRenderer; } TableCellRenderer renderer = super.getCellRenderer(row, column); if (renderer instanceof JLabel) { ((JLabel) renderer).setHorizontalAlignment(JLabel.LEFT); } return renderer; } }; private JLabel statusLabel = new JLabel() { public void setText(String text) { super.setText(text); setToolTipText(text); } }; private EnableableToolBar toolBar = new EnableableToolBar(); private boolean updatingComponents = false; private boolean initializing = false; private BasicStyleListCellRenderer basicStyleListCellRenderer = new BasicStyleListCellRenderer(); public BasicStylePanel basicStylePanel; private TableCellEditor basicStyleTableCellEditor = new TableCellEditor() { private DefaultComboBoxModel comboBoxModel = new DefaultComboBoxModel(); private BasicStyle originalStyle; private DefaultCellEditor editor; private JComboBox comboBox = new JComboBox(comboBoxModel) { public void setSelectedItem(Object anObject) { if (anObject != CUSTOM_ENTRY) { super.setSelectedItem(anObject); return; } BasicStyle style = promptBasicStyle(originalStyle); if (style == null) { return; } comboBox.addItem(style); super.setSelectedItem(style); } }; { comboBox.setRenderer(basicStyleListCellRenderer); editor = new DefaultCellEditor(comboBox); } public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) { originalStyle = (BasicStyle) value; comboBoxModel.removeAllElements(); comboBoxModel.addElement(CUSTOM_ENTRY); //Include the current BasicStyle; otherwise, the current BasicStyle //will not show up as the selected item in the combo box. [Jon Aquino] comboBoxModel.addElement(value); for (Iterator i = ColorScheme.create( (String) colorSchemeComboBox.getSelectedItem()) .getColors().iterator(); i.hasNext();) { Color color = (Color) i.next(); BasicStyle bs = new BasicStyle(color); bs.setLineWidth(getLayer().getBasicStyle().getLineWidth()); bs.setLineWidth(getLayer().getBasicStyle().getLineWidth()); bs.setRenderingLine(getLayer().getBasicStyle().isRenderingLine()); bs.setLinePattern(getLayer().getBasicStyle().getLinePattern()); bs.setRenderingFill(getLayer().getBasicStyle().isRenderingFill()); bs.setFillPattern(getLayer().getBasicStyle().getFillPattern()); bs.setRenderingVertices(getLayer().getBasicStyle().getRenderingVertices()); comboBoxModel.addElement(bs); } comboBoxModel.setSelectedItem(value); return editor.getTableCellEditorComponent(table, value, isSelected, row, column); } public Object getCellEditorValue() { return editor.getCellEditorValue(); } public boolean isCellEditable(EventObject anEvent) { return editor.isCellEditable(anEvent); } public boolean shouldSelectCell(EventObject anEvent) { return editor.shouldSelectCell(anEvent); } public boolean stopCellEditing() { return editor.stopCellEditing(); } public void cancelCellEditing() { editor.cancelCellEditing(); } public void addCellEditorListener(CellEditorListener l) { editor.addCellEditorListener(l); } public void removeCellEditorListener(CellEditorListener l) { editor.removeCellEditorListener(l); } }; private DefaultComboBoxModel comboBoxModel = new DefaultComboBoxModel(); private ColorScheme colorSchemeForInserts = null; private MyPlugIn insertPlugIn = new MyPlugIn() { public String getName() { return I18N.get("ui.renderer.style.ColorThemingPanel.insert-row"); } public Icon getIcon() { return GUIUtil.toSmallIcon(IconLoader.icon("Plus.gif")); } public boolean execute(PlugInContext context) throws Exception { reportNothingToUndoYet(context); stopCellEditing(); tableModel().insertAttributeValue((table.getSelectedRowCount() > 0) ? table.getSelectedRows()[0] : table.getRowCount(), getColorSchemeForInserts()); if (table.getSelectedRowCount() == 0) { table.scrollRectToVisible(table.getCellRect(table.getRowCount() - 1, 0, true)); } if (table.getSelectedRowCount() != 0) { int firstSelectedRow = table.getSelectedRows()[0]; table.clearSelection(); table.addRowSelectionInterval(firstSelectedRow, firstSelectedRow); } return true; } }; private MyPlugIn deletePlugIn = new MyPlugIn() { public String getName() { return I18N.get("ui.renderer.style.ColorThemingPanel.delete-row"); } public Icon getIcon() { return GUIUtil.toSmallIcon(IconLoader.icon("Delete.gif")); } public boolean execute(PlugInContext context) throws Exception { reportNothingToUndoYet(context); stopCellEditing(); tableModel().removeAttributeValues(table.getSelectedRows()); return true; } }; private JSlider transparencySlider = new JSlider(); //Maintain a blackboard for error messages instead of running all //validations whenever we need to check for messages -- some validations //may be expensive. [Jon Aquino] private HashSet errorMessages = new HashSet(); private DiscreteColorThemingState discreteColorThemingState = new DiscreteColorThemingState(table); private RangeColorThemingState rangeColorThemingState; private QuantileColorThemingState quantileColorThemingState; private MeanSTDevColorThemingState meanSTDevColorThemingState; private MaximalBreaksColorThemingState maxBreaksColorThemingState; private JenksBreaksColorThemingState jenksColorThemingState; private State state = discreteColorThemingState; public ColorThemingStylePanel(Layer layer, WorkbenchContext workbenchContext) { initializing = true; try { basicStylePanel = new BasicStylePanel( workbenchContext.getWorkbench().getBlackboard(), ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS); this.layer = layer; this.workbenchContext = workbenchContext; // 0 - DiscreteColorThemingState (already initialized) // 1 - Equal Range rangeColorThemingState = new RangeColorThemingState(this); // 2 - Quantile or Equal Number (added by obedel) quantileColorThemingState = new QuantileColorThemingState(this); // 3 - Mean-Standard-Deviation (added by sstein) meanSTDevColorThemingState = new MeanSTDevColorThemingState(this); // 4 - Maximal Breaks (added by sstein) maxBreaksColorThemingState = new MaximalBreaksColorThemingState(this); // 5 - Jenks Optimal (added by sstein) jenksColorThemingState = new JenksBreaksColorThemingState(this); initAttributeNameComboBox(layer); jbInit(); //Must set state early so that #initTable can call #fromExternalFormat. [Jon Aquino] state = colorThemingStyleHasRanges(getLayer()) ? (State) rangeColorThemingState : discreteColorThemingState; classificationComboBox.removeItemListener(classificationListener); classificationComboBox.setSelectedIndex( colorThemingStyleHasRanges(getLayer()) ? 1 : 0); classificationComboBox.addItemListener(classificationListener); //Init the table before calling #setState, in which //RangeColorThemingState will add itself as a listener //to the table model. [Jon Aquino] initTable(layer); setState(state); initColorSchemeComboBox(layer.getLayerManager()); initTransparencySlider(layer); initToolBar(); enableColorThemingCheckBox.setSelected(ColorThemingStyle.get(layer) .isEnabled()); updateComponents(); GUIUtil.sync(basicStylePanel.getTransparencySlider(), transparencySlider); basicStylePanel.setSynchronizingLineColor(layer.isSynchronizingLineColor()); } catch (Exception e) { e.printStackTrace(); } finally { initializing = false; } } private void updateBackground(JComponent component) { component.setBackground(enableColorThemingCheckBox.isSelected() ? Color.white : jPanel1.getBackground()); } private int attributeColumn() { return table.convertColumnIndexToView(ColorThemingTableModel.ATTRIBUTE_COLUMN); } private int labelColumn() { return table.convertColumnIndexToView(ColorThemingTableModel.LABEL_COLUMN); } private int colorColumn() { return table.convertColumnIndexToView(ColorThemingTableModel.COLOR_COLUMN); } public String getTitle() { return TITLE; } public void updateStyles() { boolean firingEvents = layer.getLayerManager().isFiringEvents(); layer.getLayerManager().setFiringEvents(false); try { layer.removeStyle(ColorThemingStyle.get(layer)); layer.addStyle(new ColorThemingStyle(getAttributeName(), state .toExternalFormat(tableModel() .getAttributeValueToBasicStyleMap()), state .toExternalFormat(tableModel() .getAttributeValueToLabelMap()), tableModel() .getDefaultStyle())); ColorThemingStyle.get(layer).setAlpha(getAlpha()); ColorThemingStyle.get(layer).setEnabled( enableColorThemingCheckBox.isSelected()); layer.getBasicStyle().setEnabled( !enableColorThemingCheckBox.isSelected()); // fix bug 3091363 and part of 3043312 layer.getVertexStyle().setEnabled( layer.getVertexStyle().isEnabled() && !enableColorThemingCheckBox.isSelected()); } finally { layer.getLayerManager().setFiringEvents(firingEvents); } layer.fireAppearanceChanged(); } private String getAttributeName() { return (String) attributeNameComboBox.getSelectedItem(); } private AttributeType getAttributeType() { return layer.getFeatureCollectionWrapper() .getFeatureSchema() .getAttributeType(getAttributeName()); } private void stopCellEditing() { if (table.getCellEditor() instanceof DefaultCellEditor) { ((DefaultCellEditor) table.getCellEditor()).stopCellEditing(); } } public JCheckBox getSynchronizeCheckBox() { return basicStylePanel.getSynchronizeCheckBox(); } public Layer getLayer() { return layer; } private void initTransparencySlider(Layer layer) { transparencySlider.setValue(transparencySlider.getMaximum() - ColorThemingStyle.get(layer).getDefaultStyle().getAlpha()); transparencySlider.addChangeListener(new ChangeListener() { public void stateChanged(ChangeEvent e) { basicStyleListCellRenderer.setAlpha(getAlpha()); } }); basicStyleListCellRenderer.setAlpha(getAlpha()); } private boolean colorThemingStyleHasRanges(Layer layer) { return !ColorThemingStyle.get(layer).getAttributeValueToBasicStyleMap() .isEmpty() && ColorThemingStyle.get(layer).getAttributeValueToBasicStyleMap().keySet() .iterator().next() instanceof Range; } private void initToolBar() { EnableCheck atLeast1RowMustBeSelectedCheck = new EnableCheck() { public String check(JComponent component) { return (table.getSelectedRowCount() == 0) ? I18N.get("ui.renderer.style.ColorThemingPanel.at-least-1-row-must-be-selected") : null; } }; EnableCheck layerMustHaveAtLeast1AttributeCheck = new EnableCheck() { public String check(JComponent component) { return (attributeNameComboBox.getItemCount() == 0) ? I18N.get("ui.renderer.style.ColorThemingPanel.layer-must-have-at-least-1-attribute") : null; } }; EnableCheck colorThemingMustBeEnabledCheck = new EnableCheck() { public String check(JComponent component) { return (!enableColorThemingCheckBox.isSelected()) ? I18N.get("ui.renderer.style.ColorThemingPanel.colour-theming-must-be-enabled") : null; } }; addPlugIn(insertPlugIn, new MultiEnableCheck().add(layerMustHaveAtLeast1AttributeCheck).add(colorThemingMustBeEnabledCheck)); addPlugIn(deletePlugIn, new MultiEnableCheck().add(layerMustHaveAtLeast1AttributeCheck) .add(atLeast1RowMustBeSelectedCheck).add(colorThemingMustBeEnabledCheck)); } private void addPlugIn(MyPlugIn plugIn, EnableCheck enableCheck) { JButton button = new JButton(); toolBar.add(button, plugIn.getName(), plugIn.getIcon(), AbstractPlugIn.toActionListener(plugIn, workbenchContext, null), enableCheck); } private void updateComponents() { if (updatingComponents) { return; } updatingComponents = true; try { attributeLabel.setEnabled(enableColorThemingCheckBox.isSelected()); //LDB: keep enabled always attributeNameComboBox.setEnabled(enableColorThemingCheckBox.isSelected()); state.getPanel().setEnabled( enableColorThemingCheckBox.isSelected() && (attributeNameComboBox.getItemCount() > 0)); classificationLabel.setEnabled( enableColorThemingCheckBox.isSelected() && (attributeNameComboBox.getItemCount() > 0) && (getAttributeType()==AttributeType.INTEGER || getAttributeType()==AttributeType.DOUBLE)); classificationComboBox.setEnabled( enableColorThemingCheckBox.isSelected() && (attributeNameComboBox.getItemCount() > 0) && (getAttributeType()==AttributeType.INTEGER || getAttributeType()==AttributeType.DOUBLE)); colorSchemeLabel.setEnabled( enableColorThemingCheckBox.isSelected() && (attributeNameComboBox.getItemCount() > 0)); colorSchemeComboBox.setEnabled(enableColorThemingCheckBox.isSelected() && (attributeNameComboBox.getItemCount() > 0)); table.setEnabled(enableColorThemingCheckBox.isSelected() && (attributeNameComboBox.getItemCount() > 0)); scrollPane.setEnabled(enableColorThemingCheckBox.isSelected() && (attributeNameComboBox.getItemCount() > 0)); transparencySlider.setEnabled(enableColorThemingCheckBox.isSelected() && (attributeNameComboBox.getItemCount() > 0)); statusLabel.setEnabled(enableColorThemingCheckBox.isSelected()); toolBar.updateEnabledState(); //Give the "Cannot colour-theme layer" message priority. [Jon Aquino] if (!setErrorMessage(new ErrorMessage( I18N.get("ui.renderer.style.ColorThemingPanel.cannot-colour-theme-layer-with-no-attributes")), attributeNameComboBox.getItemCount() == 0)) { setErrorMessage(new ErrorMessage( I18N.get("ui.renderer.style.ColorThemingPanel.table-must-not-be-empty")), table.getRowCount() == 0); } updateErrorDisplay(); if (table.getColumnCount() > 0) { //Column count == 0 during initialization. [Jon Aquino] table.getColumnModel() .getColumn(table.convertColumnIndexToView( ColorThemingTableModel.ATTRIBUTE_COLUMN)) .setHeaderValue(state.getAttributeValueColumnTitle()); } } finally { updatingComponents = false; } } /** * @return null if user hits Cancel */ private BasicStyle promptBasicStyle(BasicStyle basicStyle) { int originalTransparencySliderValue = transparencySlider.getValue(); basicStylePanel.setBasicStyle(basicStyle); basicStylePanel.getTransparencySlider().setValue(originalTransparencySliderValue); OKCancelPanel okCancelPanel = new OKCancelPanel(); final JDialog dialog = new JDialog((JDialog) SwingUtilities.windowForComponent( this), I18N.get("ui.renderer.style.ColorThemingPanel.custom"), true); dialog.getContentPane().setLayout(new BorderLayout()); dialog.getContentPane().add(basicStylePanel, BorderLayout.CENTER); dialog.getContentPane().add(okCancelPanel, BorderLayout.SOUTH); okCancelPanel.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { dialog.setVisible(false); } }); dialog.pack(); GUIUtil.centreOnWindow(dialog); dialog.setVisible(true); if (!okCancelPanel.wasOKPressed()) { transparencySlider.setValue(originalTransparencySliderValue); } return okCancelPanel.wasOKPressed() ? basicStylePanel.getBasicStyle() : null; } private void initTable(Layer layer) { BasicStyle defaultStyle = colorThemingStyleHasRanges(getLayer()) ? ColorThemingStyle.get(layer).getDefaultStyle(): layer.getBasicStyle(); table.setModel(new ColorThemingTableModel(defaultStyle, ColorThemingStyle.get(layer).getAttributeName(), attributeValueToBasicStyleMap(layer), attributeValueToLabelMap(layer), layer.getFeatureCollectionWrapper().getFeatureSchema()) { public Object getValueAt(int rowIndex, int columnIndex) { //Don't use #attributeColumn() here because this is in the *model*, //not the view. Otherwise, an exception will be thrown when //the user tries to move the columns. [Jon Aquino] if ((rowIndex == 0) && (columnIndex == ColorThemingTableModel.ATTRIBUTE_COLUMN)) { return state.getAllOtherValuesDescription(); } if ((rowIndex == 0) && (columnIndex == ColorThemingTableModel.LABEL_COLUMN)) { return ""; } return super.getValueAt(rowIndex, columnIndex); } }); table.createDefaultColumnsFromModel(); table.setRowSelectionAllowed(true); table.getSelectionModel().addListSelectionListener(new ListSelectionListener() { public void valueChanged(ListSelectionEvent e) { updateComponents(); } }); table.getTableHeader().addMouseListener(new MouseAdapter() { public void mousePressed(MouseEvent e) { if (SwingUtilities.isLeftMouseButton(e) && (e.getClickCount() == 1) && table.isEnabled()) { tableModel().sort(); } } }); table.getColumnModel().getColumn(colorColumn()).setCellRenderer(new TableCellRenderer() { public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { JComponent renderer = (JComponent) basicStyleListCellRenderer.getListCellRendererComponent(new JList(), value, row, isSelected, hasFocus); if (!isSelected) { updateBackground(renderer); } return renderer; } }); table.getColumnModel().getColumn(colorColumn()).setCellEditor(basicStyleTableCellEditor); table.getModel().addTableModelListener(new TableModelListener() { public void tableChanged(TableModelEvent e) { updateComponents(); Object duplicateAttributeValue = tableModel() .findDuplicateAttributeValue(); setErrorMessage(new ErrorMessage( I18N.get("ui.renderer.style.ColorThemingPanel.table-must-not-have-duplicate-attribute-values"), "(" + duplicateAttributeValue + ")"), duplicateAttributeValue != null); setErrorMessage(new ErrorMessage( I18N.get("ui.renderer.style.ColorThemingPanel.table-must-not-have-null-attribute-values")), tableModel().containsNullAttributeValues()); } }); int colorWidth = 10 + (int) basicStyleListCellRenderer.getListCellRendererComponent(new JList(), layer.getBasicStyle(), 0, false, false).getPreferredSize().getWidth(); table.getColumnModel().getColumn(colorColumn()).setPreferredWidth(colorWidth); table.getColumnModel().getColumn(colorColumn()).setMinWidth(colorWidth); table.getColumnModel().getColumn(colorColumn()).setMaxWidth(colorWidth); } private Map attributeValueToBasicStyleMap(Layer layer) { if (!colorThemingAttributeValid(layer)) { return new TreeMap(); } return state.fromExternalFormat(ColorThemingStyle.get(layer) .getAttributeValueToBasicStyleMap()); } private Map attributeValueToLabelMap(Layer layer) { if (!colorThemingAttributeValid(layer)) { return new TreeMap(); } return state.fromExternalFormat(ColorThemingStyle.get(layer) .getAttributeValueToLabelMap()); } private boolean colorThemingAttributeValid(Layer layer) { if (ColorThemingStyle.get(layer).getAttributeName() == null) { return false;} //Schema won't have attribute name if user has deleted the attribute. //[Jon Aquino] if (!layer.getFeatureCollectionWrapper().getFeatureSchema() .hasAttribute(ColorThemingStyle.get(layer) .getAttributeName())) { return false;} return true; } private void initColorSchemeComboBox(LayerManager layerManager) { colorSchemeComboBox.setRenderer(new ColorSchemeListCellRenderer() { protected void color(ColorPanel colorPanel, Color fillColor, Color lineColor) { super.color(colorPanel, GUIUtil.alphaColor(fillColor, getAlpha()), GUIUtil.alphaColor(lineColor, getAlpha())); } protected ColorScheme colorScheme(String name) { return state.filterColorScheme(super.colorScheme(name)); } }); } private int getAlpha() { return transparencySlider.getMaximum() - transparencySlider.getValue(); } private void initAttributeNameComboBox(Layer layer) { DefaultComboBoxModel model = new DefaultComboBoxModel(); for (int i = 0; i < layer.getFeatureCollectionWrapper().getFeatureSchema() .getAttributeCount(); i++) { if (i == layer.getFeatureCollectionWrapper().getFeatureSchema() .getGeometryIndex()) { continue; } String attributeName = layer.getFeatureCollectionWrapper() .getFeatureSchema() .getAttributeName(i); model.addElement(attributeName); } attributeNameComboBox.setModel(model); if (model.getSize() == 0) { //Can get here if the only attribute is the geometry. [Jon Aquino] return; } else if (model.getIndexOf(lastAttributeName) >= 0) { attributeNameComboBox.setSelectedItem(lastAttributeName); } else if (ColorThemingStyle.get(layer).getAttributeName() == null) { attributeNameComboBox.setSelectedIndex(0); lastAttributeName = getAttributeName(); } else { attributeNameComboBox.setSelectedItem( ColorThemingStyle.get(layer).getAttributeName()); lastAttributeName = getAttributeName(); } } private void jbInit() throws Exception { this.setLayout(new GridBagLayout()); jPanel1.setLayout(new GridBagLayout()); jPanel2.setLayout(new GridBagLayout()); enableColorThemingCheckBox.setText(I18N.get("ui.renderer.style.ColorThemingPanel.enable-colour-theming")); attributeLabel.setText(I18N.get("ui.renderer.style.ColorThemingPanel.attribute")+" "); statusLabel.setBorder(BorderFactory.createLoweredBevelBorder()); statusLabel.setText(" "); colorSchemeLabel.setText(I18N.get("ui.renderer.style.ColorThemingPanel.colour-scheme")+" "); attributeNameComboBox.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(ActionEvent e) { attributeNameComboBox_actionPerformed(e); } }); colorSchemeComboBox.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(ActionEvent e) { colorSchemeComboBox_actionPerformed(e); } }); enableColorThemingCheckBox.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(ActionEvent e) { enableColorThemingCheckBox_actionPerformed(e); } }); initClassificationComboBox(getAttributeType()); transparencySlider.setMaximum(255); transparencySlider.setPreferredSize(new Dimension(75, 24)); //Don't get squished by overlong status messages. [Jon Aquino] transparencySlider.setMinimumSize(new Dimension(75, 24)); transparencySlider.setToolTipText(I18N.get("ui.renderer.style.ColorThemingPanel.transparency")); transparencySlider.addChangeListener(new javax.swing.event.ChangeListener() { public void stateChanged(ChangeEvent e) { transparencySlider_stateChanged(e); } }); this.add(jPanel1, new GridBagConstraints(0, 2, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0)); // add enableColorThemingCheckBox in row 0 jPanel1.add(enableColorThemingCheckBox, new GridBagConstraints(0, 0, 4, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(2, 2, 2, 2), 0, 0)); jPanel1.add(attributeLabel, new GridBagConstraints(0, 1, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(2, 2, 2, 2), 0, 0)); // add enableColorThemingCheckBox in row 1 jPanel1.add(attributeNameComboBox, new GridBagConstraints(1, 1, 3, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(2, 2, 2, 2), 0, 0)); //jPanel1.add(fillerPanel, // new GridBagConstraints(3, 0, 1, 3, 1, 0.0, // GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, // new Insets(2, 2, 2, 2), 0, 0)); // add classification method in row 2 jPanel1.add(classificationLabel, new GridBagConstraints(0, 2, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(2, 2, 2, 2), 0, 0)); jPanel1.add(classificationComboBox, new GridBagConstraints(1, 2, 3, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(2, 2, 2, 2), 0, 0)); // let row 3 for range count options in case of classification by range // add enableColorThemingCheckBox in row 4 jPanel1.add(colorSchemeLabel, new GridBagConstraints(0, 4, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(2, 2, 2, 2), 0, 0)); jPanel1.add(colorSchemeComboBox, new GridBagConstraints(1, 4, 3, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(2, 2, 2, 2), 0, 0)); this.add(scrollPane, new GridBagConstraints(0, 4, 1, 1, 1.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(2, 2, 2, 2), 0, 0)); this.add(jPanel2, new GridBagConstraints(0, 5, 1, 1, 1.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0)); jPanel2.add(statusLabel, new GridBagConstraints(1, 0, 1, 1, 1.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0)); jPanel2.add(toolBar, new GridBagConstraints(2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0)); jPanel2.add(transparencySlider, new GridBagConstraints(3, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0)); scrollPane.getViewport().add(table); } protected void enableColorThemingCheckBox_actionPerformed(ActionEvent e) { if (table.getRowCount() == 1) { //1 == just the "All other values" row. [Jon Aquino] populateTable(); } updateComponents(); } void attributeNameComboBox_actionPerformed(ActionEvent e) { try { if (initializing) { return; } if (attributeNameComboBox.getItemCount() == 0) { return; } Object selectedItem = attributeNameComboBox.getSelectedItem(); if (selectedItem != null && selectedItem.equals(lastAttributeName)) { //Don't want to clear the table if the attribute hasn't changed. [Jon Aquino] return; } initClassificationComboBox(getAttributeType()); updateComponents(); stopCellEditing(); populateTable(); } finally { //Want to set lastAttributeName even during initialization. Otherwise, the following //undesirable behaviour occurs: Open dialog box; press Get All Values // -- the table is populated; edit the table; click on attribute combo box but click //on the same attribute -- the table is repopulated! [Jon Aquino] lastAttributeName = getAttributeName(); if (table.getModel() instanceof ColorThemingTableModel) { //Model will be DefaultTableModel during early initialization. [Jon Aquino] tableModel().setAttributeName(getAttributeName()); } } } public ColorThemingTableModel tableModel() { return (ColorThemingTableModel) table.getModel(); } private SortedSet getNonNullAttributeValues() { TreeSet values = new TreeSet(); for (Iterator i = layer.getFeatureCollectionWrapper().getFeatures() .iterator(); i.hasNext();) { Feature feature = (Feature) i.next(); if (feature.getAttribute(getAttributeName()) != null) { values.add(ColorThemingStyle.trimIfString(feature.getAttribute(getAttributeName()))); } } return values; } public void populateTable() { if (!(enableColorThemingCheckBox.isSelected() && (attributeNameComboBox.getItemCount() > 0))) { return; } stopCellEditing(); tableModel().clear(); tableModel().setMaps( toAttributeValueToBasicStyleMap(filteredAttributeValues()), toAttributeValueToLabelMap(filteredAttributeValues())); tableModel().sort(tableModel().wasLastSortAscending()); applyColorScheme(); } private Collection filteredAttributeValues() { return state.filterAttributeValues(getNonNullAttributeValues()); } private Map toAttributeValueToLabelMap(Collection attributeValues) { Map attributeValueToAttributeValueMap = new TreeMap(); for (Iterator i = attributeValues.iterator(); i.hasNext(); ) { Object attributeValue = i.next(); attributeValueToAttributeValueMap.put(attributeValue, attributeValue); } Map attributeValueToLabelMap = CollectionUtil.inverse(state.toExternalFormat(attributeValueToAttributeValueMap)); for (Iterator i = attributeValueToLabelMap.keySet().iterator(); i.hasNext(); ) { Object attributeValue = i.next(); attributeValueToLabelMap.put(attributeValue, attributeValueToLabelMap.get(attributeValue).toString()); } return attributeValueToLabelMap; } private Map toAttributeValueToBasicStyleMap(Collection attributeValues) { Map attributeValueToBasicStyleMap = new TreeMap(); for (Iterator i = attributeValues.iterator(); i.hasNext();) { Object attributeValue = i.next(); attributeValueToBasicStyleMap.put(attributeValue, getLayer().getBasicStyle()); } return attributeValueToBasicStyleMap; } void colorSchemeComboBox_actionPerformed(ActionEvent e) { if (initializing) { return; } stopCellEditing(); layer.getLayerManager().getBlackboard().put(COLOR_SCHEME_KEY, colorSchemeComboBox.getSelectedItem()); applyColorScheme(); colorSchemeForInserts = null; } private ColorScheme getColorSchemeForInserts() { //Cache the colour-scheme; otherwise the first color will always //be used for inserts. [Jon Aquino] if ((colorSchemeForInserts == null) || !colorSchemeForInserts.getName().equalsIgnoreCase((String) colorSchemeComboBox.getSelectedItem())) { colorSchemeForInserts = ColorScheme.create((String) colorSchemeComboBox.getSelectedItem()); } return colorSchemeForInserts; } public void applyColorScheme() { stopCellEditing(); state.applyColorScheme(state.filterColorScheme(ColorScheme.create( (String) colorSchemeComboBox.getSelectedItem()))); } private void cancelCellEditing() { if (table.getCellEditor() instanceof DefaultCellEditor) { ((DefaultCellEditor) table.getCellEditor()).cancelCellEditing(); } } public String validateInput() { stopCellEditing(); //Split into #validateInput and #internalValidateInput to avoid //infinite recursion (#stopCellEditing causes #fireTableChanged //which causes #validateInput which causes #stopCellEditing...) //[Jon Aquino] return internalValidateInput(); } private String internalValidateInput() { if (!enableColorThemingCheckBox.isSelected()) { return null; } if (errorMessages.isEmpty()) { return null; } return errorMessages.iterator().next().toString(); } /** * @return enabled */ private boolean setErrorMessage(ErrorMessage message, boolean enabled) { //Always remove the original, because #add doesn't replace, but is //ignored if the message already exists. Need to replace because the //"specific part" of the error message may be new. [Jon Aquino] errorMessages.remove(message); if (enabled) { errorMessages.add(message); } updateErrorDisplay(); return enabled; } private void updateErrorDisplay() { String errorMessage = internalValidateInput(); if (errorMessage != null) { statusLabel.setText(errorMessage); statusLabel.setIcon(GUIUtil.toSmallIcon(IconLoader.icon( "Delete.gif"))); } else { statusLabel.setText(" "); statusLabel.setIcon(null); } } private void setState(State state) { this.state.deactivate(); jPanel1.remove(this.state.getPanel()); //Need to call #revalidate; otherwise, the component won't get //removed. [Jon Aquino] this.state = state; jPanel1.add(state.getPanel(), new GridBagConstraints(0, 3, 4, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(2, 2, 2, 2), 0, 0)); initializing = true; try { colorSchemeComboBox.setModel(new DefaultComboBoxModel( new Vector(state.getColorSchemeNames()))); //colorSchemeComboBox.setPrototypeDisplayValue("01234567890123456789012345678901"); //The colour scheme may not be present if it was taken from the //set of discrete colour schemes but the combo box currently contains //the range colour schemes. [Jon Aquino] colorSchemeComboBox.setSelectedItem(layer.getLayerManager() .getBlackboard().get(COLOR_SCHEME_KEY, colorSchemeComboBox.getItemAt(0))); } finally { initializing = false; } updateComponents(); this.state.activate(); jPanel1.repaint(); } void transparencySlider_stateChanged(ChangeEvent e) { repaint(); } public JSlider getTransparencySlider() { return transparencySlider; } public JTable getTable() { return table; } public static interface State { public String getAllOtherValuesDescription(); public ColorScheme filterColorScheme(ColorScheme colorScheme); public void activate(); public void deactivate(); public Collection getColorSchemeNames(); public void applyColorScheme(ColorScheme scheme); public Collection filterAttributeValues(SortedSet attributeValues); public String getAttributeValueColumnTitle(); public JComponent getPanel(); /** * Performs any necessary modifications to the map before applying * it to the layer. */ public Map toExternalFormat(Map attributeValueToObjectMap); public Map fromExternalFormat(Map attributeValueToObjectMap); } private abstract class MyPlugIn extends AbstractPlugIn { public abstract Icon getIcon(); } // TODO this class does not depend on this package, it couldbe moved to // com.vividsolutions.jump.util private class ErrorMessage { private String commonPart; private String specificPart; public ErrorMessage(String commonPart) { this(commonPart, ""); } public ErrorMessage(String commonPart, String specificPart) { this.commonPart = commonPart; this.specificPart = specificPart; } public int hashCode() { return commonPart.hashCode(); } public boolean equals(Object obj) { return commonPart.equals(((ErrorMessage) obj).commonPart); } public String toString() { return commonPart + specificPart; } } //-- obedel [12/10/2005] // return an ordered map (value -> count of corresponding features) // Erwan Bocher [20/01/2005] // Add button to calculate range and populateTable public SortedMap getAttributeValuesCount() { TreeMap values = new TreeMap(); for (Iterator i = layer.getFeatureCollectionWrapper().getFeatures() .iterator(); i.hasNext();) { Feature feature = (Feature) i.next(); if (feature.getAttribute(getAttributeName()) != null) { Object key = ColorThemingStyle.trimIfString(feature.getAttribute(getAttributeName())); Integer count = (Integer) values.get(key); if (count==null) values.put(key,new Integer(1)); else values.put(key,new Integer(count.intValue()+1)); } } return values; } private class ActionClassification implements ItemListener { public void itemStateChanged(ItemEvent arg0) { switch (classificationComboBox.getSelectedIndex()) { case UNIQUE_VALUE: setState(discreteColorThemingState); break; case EQUAL_INTERVAL: setState((State) rangeColorThemingState); break; case QUANTILE: //i.e. equal number setState((State) quantileColorThemingState); break; case MEAN_STDEV: setState((State) meanSTDevColorThemingState); break; case MAX_BREAKS: setState((State) maxBreaksColorThemingState); break; case JENKS: setState((State) jenksColorThemingState); break; } populateTable(); applyColorScheme(); } } private ActionClassification classificationListener = new ActionClassification(); private static final int UNIQUE_VALUE = 0; private static final int EQUAL_INTERVAL = 1; private static final int QUANTILE = 2; private static final int MEAN_STDEV = 3; private static final int MAX_BREAKS = 4; private static final int JENKS = 5; private void initClassificationComboBox(AttributeType type) { classificationComboBox.removeItemListener(classificationListener); classificationComboBox.removeAllItems(); classificationComboBox.addItem(I18N.get("ui.renderer.style.ColorThemingStylePanel.Unique-value")); if (type == AttributeType.INTEGER || type == AttributeType.DOUBLE) { classificationComboBox.addItem(I18N.get("ui.renderer.style.ColorThemingStylePanel.Equal-Interval")); classificationComboBox.addItem(I18N.get("ui.renderer.style.ColorThemingStylePanel.Quantile-Equal-Number")); // -- [sstein 15.Feb.2009] classificationComboBox.addItem(I18N.get("ui.renderer.style.ColorThemingStylePanel.Mean-Standard-Deviation")); classificationComboBox.addItem(I18N.get("ui.renderer.style.ColorThemingStylePanel.Maximal-Breaks")); classificationComboBox.addItem(I18N.get("ui.renderer.style.ColorThemingStylePanel.Jenks-Optimal-Method")); } classificationComboBox.addItemListener(classificationListener); if (classificationComboBox.getItemCount() == 1 && (state != discreteColorThemingState)) { setState(discreteColorThemingState); } } }