/*---------------- FILE HEADER ------------------------------------------ This file is part of deegree. Copyright (C) 2001-2012 by: Department of Geography, University of Bonn http://www.giub.uni-bonn.de/deegree/ lat/lon GmbH http://www.lat-lon.de This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Contact: lat/lon GmbH Aennchenstr. 19 53177 Bonn Germany E-Mail: info@lat-lon.de Prof. Dr. Klaus Greve Department of Geography University of Bonn Meckenheimer Allee 166 53115 Bonn Germany E-Mail: greve@giub.uni-bonn.de ---------------------------------------------------------------------------*/ package org.deegree.igeo.views.swing.style.component.classification; import static org.deegree.igeo.i18n.Messages.get; import static org.deegree.igeo.style.model.classification.Column.COLUMNTYPE.FILLCOLOR; import static org.deegree.igeo.style.model.classification.Column.COLUMNTYPE.LINESTYLE; import static org.deegree.igeo.style.model.classification.Column.COLUMNTYPE.VALUE; import static org.deegree.igeo.style.model.classification.ThematicGroupingInformation.GROUPINGTYPE.EQUAL; import static org.deegree.igeo.style.model.classification.ThematicGroupingInformation.GROUPINGTYPE.QUALITY; import static org.deegree.igeo.style.model.classification.ThematicGroupingInformation.GROUPINGTYPE.QUANTILE; import static org.deegree.igeo.style.model.classification.ThematicGroupingInformation.GROUPINGTYPE.UNIQUE; import java.awt.BorderLayout; import java.awt.Dimension; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseEvent; import java.awt.event.WindowAdapter; import java.math.BigInteger; import java.net.MalformedURLException; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Map; import javax.swing.ButtonGroup; import javax.swing.DefaultCellEditor; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComboBox; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JRadioButton; import javax.swing.JScrollPane; import javax.swing.JSpinner; import javax.swing.JTable; import javax.swing.ListSelectionModel; import javax.swing.SpinnerModel; import javax.swing.SpinnerNumberModel; import javax.swing.event.ChangeEvent; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import javax.swing.event.TableModelEvent; import javax.swing.event.TableModelListener; import javax.swing.table.JTableHeader; import javax.swing.table.TableCellEditor; import javax.swing.table.TableCellRenderer; import javax.swing.table.TableColumn; import javax.swing.table.TableColumnModel; import org.deegree.datatypes.QualifiedName; import org.deegree.datatypes.Types; import org.deegree.framework.log.ILogger; import org.deegree.framework.log.LoggerFactory; import org.deegree.graphics.sld.Rule; import org.deegree.igeo.style.LayerCache.CachedLayer; import org.deegree.igeo.style.classification.ClassificationFromSld; import org.deegree.igeo.style.model.DashArray; import org.deegree.igeo.style.model.GraphicSymbol; import org.deegree.igeo.style.model.Histogram; import org.deegree.igeo.style.model.PropertyValue; import org.deegree.igeo.style.model.RandomColors; import org.deegree.igeo.style.model.SldProperty; import org.deegree.igeo.style.model.SldValues; import org.deegree.igeo.style.model.WellKnownMark; import org.deegree.igeo.style.model.classification.AbstractClassification; import org.deegree.igeo.style.model.classification.ClassificationTableRow; import org.deegree.igeo.style.model.classification.Column; import org.deegree.igeo.style.model.classification.EqualIntervalClassification; import org.deegree.igeo.style.model.classification.IllegalClassificationException; import org.deegree.igeo.style.model.classification.Intervallable; import org.deegree.igeo.style.model.classification.Intervallables.DateIntervallable; import org.deegree.igeo.style.model.classification.Intervallables.DoubleIntervallable; import org.deegree.igeo.style.model.classification.Intervallables.StringIntervallable; import org.deegree.igeo.style.model.classification.ManualClassification; import org.deegree.igeo.style.model.classification.QualityClassification; import org.deegree.igeo.style.model.classification.QuantileClassification; import org.deegree.igeo.style.model.classification.ThematicGrouping; import org.deegree.igeo.style.model.classification.ThematicGroupingInformation; import org.deegree.igeo.style.model.classification.ThematicGroupingInformation.GROUPINGTYPE; import org.deegree.igeo.style.model.classification.UniqueValueGrouping; import org.deegree.igeo.style.model.classification.ValueRange; import org.deegree.igeo.views.swing.addlayer.QualifiedNameRenderer; import org.deegree.igeo.views.swing.style.SingleItem; import org.deegree.igeo.views.swing.style.SingleItemDisableComboBox; import org.deegree.igeo.views.swing.style.StyleDialog; import org.deegree.igeo.views.swing.style.StyleDialogUtils; import org.deegree.igeo.views.swing.style.VisualPropertyPanel; import org.deegree.igeo.views.swing.style.component.font.FontHelper; import org.deegree.igeo.views.swing.style.editor.ClassificationValuesEditor; import org.deegree.igeo.views.swing.style.editor.ColorTableCellEditor; import org.deegree.igeo.views.swing.style.editor.FillTableCellEditor; import org.deegree.igeo.views.swing.style.editor.PointCellEditor; import org.deegree.igeo.views.swing.style.editor.SpinnerTableCellEditor; import org.deegree.igeo.views.swing.style.renderer.ClassificationValuesRenderer; import org.deegree.igeo.views.swing.style.renderer.ColorTableCellRenderer; import org.deegree.igeo.views.swing.style.renderer.DashArrayRenderer; import org.deegree.igeo.views.swing.style.renderer.LocaleTableCellRenderer; import org.deegree.igeo.views.swing.style.renderer.SldPropertyCellRenderer; import org.deegree.igeo.views.swing.style.renderer.SldPropertyRenderer; import org.deegree.igeo.views.swing.style.renderer.SymbolRenderer; import org.deegree.igeo.views.swing.style.renderer.SymbolTableCellRenderer; import org.deegree.igeo.views.swing.util.IconRegistry; import org.deegree.model.feature.schema.FeatureType; import org.deegree.model.filterencoding.FilterEvaluationException; import org.deegree.model.filterencoding.PropertyName; import org.deegree.ogcbase.PropertyPath; import org.deegree.ogcbase.PropertyPathFactory; import com.jgoodies.forms.builder.ButtonBarBuilder; import com.jgoodies.forms.builder.DefaultFormBuilder; import com.jgoodies.forms.layout.CellConstraints; import com.jgoodies.forms.layout.FormLayout; /** * <code>AbstractClassificationPanel</code> TODO add class documentation * * @author <a href="mailto:wanhoff@lat-lon.de">Jeronimo Wanhoff</a> * @author <a href="mailto:buesching@lat-lon.de">Lyn Buesching</a> * @author last edited by: $Author$ * * @version $Revision$, $Date$ * */ public abstract class AbstractClassificationPanel extends JPanel implements ActionListener, TableModelListener { private static final long serialVersionUID = -7782382934136012149L; private static final ILogger LOG = LoggerFactory.getLogger( AbstractClassificationPanel.class ); private SingleItem unknownClassification; private SingleItem uniqueValue; private SingleItem equalInterval; private SingleItem quantile; private SingleItem quality; public enum SYMBOLIZERTYPE { POLYGON, POINT, LINE, LABEL } protected VisualPropertyPanel assignedVisualPropPanel; protected JComboBox propertyCB; private ClassificationTableModel<?> model; private JCheckBox status; private SingleItemDisableComboBox classificationTypeCB; private JLabel isManual = new JLabel( get( "$MD10735" ) ); private JSpinner numberOfClassesSpinner; private JTable classesTable; private boolean isUpdating = false; private boolean isInitialising = true; private JButton addRowBt; private JButton removeRowBt; private JButton openHistogramBt; private String datePattern; private String decimalPattern; private Histogram histogram; private JRadioButton fullDatabase; private JRadioButton extentDatabase; /** * * @param assignedVisualPropPanel */ public AbstractClassificationPanel( VisualPropertyPanel assignedVisualPropPanel ) { this.assignedVisualPropPanel = assignedVisualPropPanel; datePattern = assignedVisualPropPanel.getOwner().getSettings().getFormatsOptions().getPattern( "datePattern" ); decimalPattern = assignedVisualPropPanel.getOwner().getSettings().getFormatsOptions().getPattern( "decimalPattern" ); setLayout( new BorderLayout() ); init(); } /** * @return true, if classification is enabled; false otherwise */ public boolean isActive() { return status.isSelected(); } /** * @return the list of rules, representing the classification */ public List<Rule> getRules() { PropertyName propertyName = new PropertyName( (QualifiedName) propertyCB.getSelectedItem() ); return model.getClassifiedData( getSymbolizerType(), propertyName ); } /** * Constructs a manual classification out of the given rules. Only the first symbolizer of each rule will be * considered, if it is a PolygonSymbolizer!. * * @param the * rules to construct a classification * @param type * the type of the featuretypeName * @throws FilterEvaluationException */ public void setValues( List<Rule> rules, FeatureType featureType ) throws IllegalClassificationException, FilterEvaluationException { PropertyName propertyName = ClassificationFromSld.detectPropertyName( rules ); if ( propertyName == null ) { throw new IllegalClassificationException( "PopertyNames are not valid!" ); } // TODO: check if this is necessary!? boolean isTypeCorrect = ClassificationFromSld.isTypeCorrect( rules, getSymbolizerType() ); if ( !isTypeCorrect ) { throw new IllegalClassificationException( "Could not create list of value ranges - symbolizer is not of type " + getSymbolizerType() ); } int propertyType = ClassificationFromSld.detectPropertyType( propertyName, featureType ); switch ( propertyType ) { case Types.INTEGER: case Types.SMALLINT: case Types.BIGINT: case Types.DOUBLE: case Types.FLOAT: setPropertyCB( propertyName ); PropertyValue<?> pvDouble = getPropertyValue(); List<Intervallable<Double>> doubleData = getDoubleData( pvDouble ); ThematicGroupingInformation<Double> tgiDouble = ClassificationFromSld.createDoubleClassification( rules, doubleData, propertyType, decimalPattern, assignedVisualPropPanel.getOwner().getSettings() ); ThematicGrouping<Double> groupingDouble; groupingDouble = getGrouping( tgiDouble ); numberOfClassesSpinner.setValue( tgiDouble.getRows().size() ); groupingDouble.setData( doubleData ); ClassificationTableModel<Double> tableModelDouble = new ClassificationTableModel<Double>( getColumns(), assignedVisualPropPanel.getOwner() ); tableModelDouble.setClassification( tgiDouble.getRows(), tgiDouble.getType() ); tableModelDouble.setThematicGrouping( groupingDouble, tgiDouble.getType() ); configureClassesTable( tableModelDouble, groupingDouble.getAttributeHeader(), new ClassificationValuesRenderer<Double>(), new ClassificationValuesEditor<Double>( new DoubleIntervallable( 0.0, decimalPattern ) ) ); break; case Types.DATE: setPropertyCB( propertyName ); PropertyValue<?> pvDate = getPropertyValue(); List<Intervallable<Date>> dateData = getDateData( pvDate ); ThematicGroupingInformation<Date> tgiDate = ClassificationFromSld.createDateClassification( rules, dateData, propertyType, datePattern, assignedVisualPropPanel.getOwner().getSettings() ); ThematicGrouping<Date> groupingDate; groupingDate = getGrouping( tgiDate ); numberOfClassesSpinner.setValue( tgiDate.getRows().size() ); groupingDate.setData( dateData ); ClassificationTableModel<Date> tableModelDate = new ClassificationTableModel<Date>( getColumns(), assignedVisualPropPanel.getOwner() ); tableModelDate.setClassification( tgiDate.getRows(), tgiDate.getType() ); tableModelDate.setThematicGrouping( groupingDate, tgiDate.getType() ); configureClassesTable( tableModelDate, groupingDate.getAttributeHeader(), new ClassificationValuesRenderer<Date>(), new ClassificationValuesEditor<Date>( new DateIntervallable( new Date(), datePattern ) ) ); break; case Types.VARCHAR: setPropertyCB( propertyName ); PropertyValue<?> pvString = getPropertyValue(); List<Intervallable<String>> stringData = getStringData( pvString ); ThematicGroupingInformation<String> tgiString = ClassificationFromSld.createStringClassification( rules, stringData, propertyType, assignedVisualPropPanel.getOwner().getSettings() ); ThematicGrouping<String> groupingString; switch ( tgiString.getType() ) { case UNIQUE: groupingString = new UniqueValueGrouping<String>(); classificationTypeCB.setSelectedItem( uniqueValue ); numberOfClassesSpinner.setEnabled( false ); break; case QUALITY: groupingString = new QualityClassification<String>(); classificationTypeCB.setSelectedItem( quality ); numberOfClassesSpinner.setEnabled( false ); break; default: groupingString = new ManualClassification<String>(); numberOfClassesSpinner.setValue( tgiString.getRows().size() ); unknownClassification.setEnabled( true ); classificationTypeCB.setSelectedItem( unknownClassification ); break; } numberOfClassesSpinner.setValue( tgiString.getRows().size() ); numberOfClassesSpinner.setEnabled( false ); groupingString.setData( stringData ); ClassificationTableModel<String> tableModelString = new ClassificationTableModel<String>( getColumns(), assignedVisualPropPanel.getOwner() ); tableModelString.setClassification( tgiString.getRows(), tgiString.getType() ); tableModelString.setThematicGrouping( groupingString, tgiString.getType() ); configureClassesTable( tableModelString, groupingString.getAttributeHeader(), new ClassificationValuesRenderer<String>(), new ClassificationValuesEditor<String>( new StringIntervallable( "dummy" ) ) ); break; default: LOG.logInfo( "not supported type: " + propertyType ); break; } status.setSelected( true ); } private PropertyValue<?> getPropertyValue() { StyleDialog styleDialog = assignedVisualPropPanel.getOwner(); if ( fullDatabase.isSelected() ) { return styleDialog.getAllPropertyValue( (QualifiedName) this.propertyCB.getSelectedItem() ); } else if ( extentDatabase.isSelected() ) { return styleDialog.getExtentPropertyValue( (QualifiedName) this.propertyCB.getSelectedItem() ); } return styleDialog.getPropertyValue( (QualifiedName) this.propertyCB.getSelectedItem() ); } private <V extends Comparable<V>> ThematicGrouping<V> getGrouping( ThematicGroupingInformation<V> thematicGrouping ) { GROUPINGTYPE type = thematicGrouping.getType(); // TODO: ask user for classification // if ( !thematicGrouping.getType().equals( UNIQUE ) && !thematicGrouping.getType().equals( QUANTILE ) // && !thematicGrouping.getType().equals( EQUAL ) && !thematicGrouping.getType().equals( QUALITY ) ) { // type = askUserForClassificationType(); // } ThematicGrouping<V> grouping; switch ( type ) { case UNIQUE: grouping = new UniqueValueGrouping<V>(); classificationTypeCB.setSelectedItem( uniqueValue ); numberOfClassesSpinner.setEnabled( false ); break; case QUANTILE: grouping = new QuantileClassification<V>(); classificationTypeCB.setSelectedItem( quantile ); break; case EQUAL: grouping = new EqualIntervalClassification<V>(); classificationTypeCB.setSelectedItem( equalInterval ); break; case QUALITY: grouping = new QualityClassification<V>(); classificationTypeCB.setSelectedItem( quality ); break; default: grouping = new ManualClassification<V>(); unknownClassification.setEnabled( true ); classificationTypeCB.setSelectedItem( unknownClassification ); break; } return grouping; } protected ClassificationTableModel<?> getModel() { if ( model == null ) { updateTable(); } return model; } private void init() { status = new JCheckBox(); status.addActionListener( new ActionListener() { public void actionPerformed( ActionEvent e ) { if ( isActive() ) { assignedVisualPropPanel.setActive( true ); } } } ); classesTable = new ClassesTable(); // nr of properties which can be classified List<QualifiedName> pvList = assignedVisualPropPanel.getOwner().getPropertyNames( Types.FLOAT, Types.DOUBLE, Types.INTEGER, Types.SMALLINT, Types.BIGINT, Types.DATE ); List<QualifiedName> pvListString = assignedVisualPropPanel.getOwner().getPropertyNames( Types.VARCHAR ); boolean isStringClassEnabled = true; boolean isClassEnabled = true; if ( pvList.size() == 0 ) { isClassEnabled = false; if ( pvListString.size() == 0 ) { isStringClassEnabled = false; } } uniqueValue = new SingleItem( get( "$MD10724" ), true ); equalInterval = new SingleItem( get( "$MD10725" ), isClassEnabled ); quantile = new SingleItem( get( "$MD10726" ), isClassEnabled ); quality = new SingleItem( get( "$MD11531" ), isStringClassEnabled ); unknownClassification = new SingleItem( get( "$MD11849" ), false ); List<SingleItem> classificationItems = new ArrayList<SingleItem>( 4 ); classificationItems.add( uniqueValue ); classificationItems.add( equalInterval ); classificationItems.add( quantile ); classificationItems.add( quality ); classificationItems.add( unknownClassification ); classificationTypeCB = new SingleItemDisableComboBox( classificationItems ); classificationTypeCB.addActionListener( this ); propertyCB = new JComboBox(); QualifiedNameRenderer renderer = new QualifiedNameRenderer(); renderer.setPreferredSize( new Dimension( 100, 18 ) ); propertyCB.setRenderer( renderer ); propertyCB.addActionListener( this ); SpinnerModel noClassesModel = new SpinnerNumberModel( AbstractClassification.DEFAULTNOOFCLASSES, 1, Integer.MAX_VALUE, 1 ); numberOfClassesSpinner = new JSpinner( noClassesModel ); numberOfClassesSpinner.addChangeListener( new javax.swing.event.ChangeListener() { public void stateChanged( ChangeEvent e ) { if ( !isManual.isVisible() ) { model.getThematicGrouping().setNoOfClasses( (Integer) numberOfClassesSpinner.getValue() ); model.update( VALUE, true ); } setClassificationActive(); } } ); openHistogramBt = new JButton( get( "$MD11049" ) ); openHistogramBt.addActionListener( this ); openHistogramBt.setVisible( true ); histogram = new Histogram(); histogram.addWindowListener( new WindowAdapter() { public void windowClosed( java.awt.event.WindowEvent e ) { openHistogramBt.setText( get( "$MD11049" ) ); }; } ); initDatabaseButtonGroup(); classificationTypeCB.setSelectedItem( uniqueValue ); isManual.setVisible( false ); addRowBt = new JButton( get( "$MD10739" ), IconRegistry.getIcon( "textfield_add.png" ) ); addRowBt.addActionListener( this ); removeRowBt = new JButton( get( "$MD10740" ), IconRegistry.getIcon( "textfield_delete.png" ) ); removeRowBt.setEnabled( false ); removeRowBt.addActionListener( this ); // set selection mode classesTable.setRowSelectionAllowed( true ); classesTable.setColumnSelectionAllowed( false ); classesTable.setSelectionMode( ListSelectionModel.MULTIPLE_INTERVAL_SELECTION ); classesTable.getSelectionModel().addListSelectionListener( new ListSelectionListener() { public void valueChanged( ListSelectionEvent e ) { if ( classesTable.getSelectedRows().length > 0 ) { removeRowBt.setEnabled( true ); } else { removeRowBt.setEnabled( false ); } } } ); // mouseListener classesTable.addMouseListener( new TableMouseListener( classesTable ) ); // set column model (width of the columns and editors) TableColumnModel columnModel = classesTable.getColumnModel(); for ( int i = 0; i < classesTable.getColumnCount(); i++ ) { TableColumn column = columnModel.getColumn( i ); switch ( model.getColumnType( i ) ) { case VALUE: column.setPreferredWidth( 100 ); break; case FILLCOLOR: case LINECOLOR: case FILLTRANSPARENCY: case LINETRANSPARENCY: case FONTTRANSPARENCY: case LINEWIDTH: case LINESTYLE: case SIZE: case LINECAP: case FONTFAMILY: case HALOCOLOR: case HALORADIUS: case FONTCOLOR: case FONTSIZE: case FONTSTYLE: case FONTWEIGHT: case ROTATION: column.setPreferredWidth( 75 ); break; case SYMBOL: column.setPreferredWidth( 100 ); break; } } updateTable(); int nrOfClasses = 6; if ( model != null ) { nrOfClasses = model.getRows().size(); } numberOfClassesSpinner.setValue( nrOfClasses ); // numberOfClassesSpinner.setValue( (Integer) model.getRows().size() ); JTableHeader header = classesTable.getTableHeader(); header.addMouseListener( new TableHeaderMouseListener( classesTable, assignedVisualPropPanel ) ); header.setReorderingAllowed( false ); JScrollPane scrollPane = new JScrollPane( classesTable ); classesTable.setPreferredScrollableViewportSize( new Dimension( 150, 190 ) ); // layout FormLayout fl = new FormLayout( "10dlu, left:min:grow(0.2), min:grow(0.3), $rgap, min:grow(0.3), $rgap, min:grow(0.2)", "bottom:15dlu, $cpheight, $cpheight, $cpheight, default:grow(1.0), $cpheight" ); DefaultFormBuilder builder = new DefaultFormBuilder( fl ); builder.setBorder( StyleDialogUtils.createStyleAttributeBorder( get( "$MD10719" ) ) ); CellConstraints cc = new CellConstraints(); builder.add( status, cc.xy( 1, 1 ) ); builder.addLabel( get( "$MD10720" ), cc.xy( 2, 1 ) ); builder.addLabel( get( "$MD10721" ), cc.xy( 2, 2 ) ); builder.add( classificationTypeCB, cc.xy( 3, 2 ) ); builder.add( isManual, cc.xy( 5, 2 ) ); builder.addLabel( get( "$MD10722" ), cc.xy( 2, 3 ) ); builder.add( propertyCB, cc.xywh( 3, 3, 3, 1 ) ); builder.add( openHistogramBt, cc.xy( 5, 4 ) ); builder.addLabel( get( "$MD10723" ), cc.xy( 2, 4 ) ); builder.add( numberOfClassesSpinner, cc.xy( 3, 4 ) ); builder.add( getDatabasePanel(), cc.xywh( 7, 2, 1, 3 ) ); builder.add( scrollPane, cc.xyw( 1, 5, 7 ) ); builder.add( buildTableChangerButtonBar(), cc.xyw( 1, 6, 5, CellConstraints.CENTER, CellConstraints.BOTTOM ) ); add( builder.getPanel(), BorderLayout.CENTER ); isInitialising = false; } private void initDatabaseButtonGroup() { fullDatabase = new JRadioButton( get( "$MD11867" ) ); fullDatabase.addActionListener( this ); extentDatabase = new JRadioButton( get( "$MD11868" ) ); extentDatabase.addActionListener( this ); ButtonGroup bgDatabase = new ButtonGroup(); bgDatabase.add( fullDatabase ); bgDatabase.add( extentDatabase ); extentDatabase.setSelected( true ); } private JPanel getDatabasePanel() { FormLayout fl = new FormLayout( "10dlu, left:default", "default, default, default" ); DefaultFormBuilder builder = new DefaultFormBuilder( fl ); CellConstraints cc = new CellConstraints(); builder.addLabel( get( "$MD11866" ), cc.xyw( 1, 1, 2 ) ); builder.add( extentDatabase, cc.xy( 2, 2 ) ); builder.add( fullDatabase, cc.xy( 2, 3 ) ); JPanel panel = builder.getPanel(); CachedLayer cachedLayer = assignedVisualPropPanel.getOwner().getCachedLayer(); if ( cachedLayer.isFullLoadingSupported() ) { panel.setVisible( true ); } else { panel.setVisible( false ); } return panel; } private void setPropertyCB( PropertyName propertyName ) throws IllegalClassificationException { boolean isPropertyNameValid = false; // init Property ComboBox for ( int i = 0; i < propertyCB.getItemCount(); i++ ) { PropertyPath itemAsPath = PropertyPathFactory.createPropertyPath( (QualifiedName) propertyCB.getItemAt( i ) ); if ( ClassificationFromSld.equalsPropertyNameWithotNS( itemAsPath, propertyName.getValue() ) ) { propertyCB.setSelectedIndex( i ); isPropertyNameValid = true; break; } } if ( !isPropertyNameValid ) { throw new IllegalClassificationException( "property name used in the classification is not available for classification" ); } } private void configureClassesTable( ClassificationTableModel<?> model, String valueHeader, ClassificationValuesRenderer<?> renderer, ClassificationValuesEditor<?> editor ) { classesTable.setModel( model ); int valueIndex = model.getColumnIndex( VALUE ); classesTable.getColumnModel().getColumn( valueIndex ).setHeaderValue( valueHeader ); classesTable.getColumnModel().getColumn( valueIndex ).setCellRenderer( renderer ); classesTable.getColumnModel().getColumn( valueIndex ).setCellEditor( editor ); model.addTableModelListener( this ); this.model = model; editor.addCellEditorListener( model ); } // returns a list of double intervallables for TYPES.INTEGER and TYPES.DOUBLE types private List<Intervallable<Double>> getDoubleData( PropertyValue<?> pv ) { List<Intervallable<Double>> data = new ArrayList<Intervallable<Double>>(); for ( Object o : pv.getValues() ) { double doubleValue = Double.NaN; try { if ( o != null && o instanceof String && ( (String) o ).length() > 0 ) { doubleValue = Double.parseDouble( (String) o ); } else if ( o instanceof Number ) { doubleValue = ( (Number) o ).doubleValue(); } } catch ( Exception e ) { LOG.logError( "Could not cast value to double, where type is INTEGER or DOUBLE" ); } data.add( new DoubleIntervallable( doubleValue, decimalPattern ) ); } return data; } private List<Intervallable<Date>> getDateData( PropertyValue<?> pv ) { List<Intervallable<Date>> data = new ArrayList<Intervallable<Date>>(); for ( Object o : pv.getValues() ) { data.add( new DateIntervallable( (Date) o, datePattern ) ); } return data; } private List<Intervallable<String>> getStringData( PropertyValue<?> pv ) { List<Intervallable<String>> data = new ArrayList<Intervallable<String>>(); for ( Object o : pv.getValues() ) { String stringValue = ""; try { if ( o != null && ( (String) o ).length() > 0 ) { stringValue = (String) o; } } catch ( Exception e ) { LOG.logError( "Could not cast value to string, where type is VARCHAR" ); } data.add( new StringIntervallable( stringValue ) ); } return data; } private void updateTable() { if ( this.propertyCB.getSelectedItem() != null ) { PropertyValue<?> pv = getPropertyValue(); switch ( pv.getDatatyp() ) { case Types.INTEGER: case Types.SMALLINT: case Types.BIGINT: List<Intervallable<Double>> dataInteger = new ArrayList<Intervallable<Double>>(); for ( Object o : pv.getValues() ) { if ( pv.getDatatyp() == Types.BIGINT ) { dataInteger.add( new DoubleIntervallable( ( (BigInteger) o ).doubleValue(), decimalPattern ) ); } else { dataInteger.add( new DoubleIntervallable( (double) ( (Integer) o ), decimalPattern ) ); } } ClassificationTableModel<Double> tableModelInteger = new ClassificationTableModel<Double>( getColumns(), assignedVisualPropPanel.getOwner() ); ThematicGrouping<Double> groupingInteger = getGrouping( dataInteger, (Integer) numberOfClassesSpinner.getValue() ); tableModelInteger.setThematicGrouping( groupingInteger, getGroupingType() ); configureClassesTable( tableModelInteger, groupingInteger.getAttributeHeader(), new ClassificationValuesRenderer<Double>(), new ClassificationValuesEditor<Double>( new DoubleIntervallable( 0.0, decimalPattern ) ) ); break; case Types.DOUBLE: case Types.FLOAT: List<Intervallable<Double>> dataDouble = new ArrayList<Intervallable<Double>>(); for ( Object o : pv.getValues() ) { dataDouble.add( new DoubleIntervallable( (Double) o, decimalPattern ) ); } ClassificationTableModel<Double> tableModelDouble = new ClassificationTableModel<Double>( getColumns(), assignedVisualPropPanel.getOwner() ); ThematicGrouping<Double> groupingDouble = getGrouping( dataDouble, (Integer) numberOfClassesSpinner.getValue() ); tableModelDouble.setThematicGrouping( groupingDouble, getGroupingType() ); configureClassesTable( tableModelDouble, groupingDouble.getAttributeHeader(), new ClassificationValuesRenderer<Double>(), new ClassificationValuesEditor<Double>( new DoubleIntervallable( 0.0, decimalPattern ) ) ); break; case Types.DATE: ClassificationTableModel<Date> tableModelDate = new ClassificationTableModel<Date>( getColumns(), assignedVisualPropPanel.getOwner() ); List<Intervallable<Date>> dataDate = new ArrayList<Intervallable<Date>>(); for ( Object o : pv.getValues() ) { dataDate.add( new DateIntervallable( (Date) o, datePattern ) ); } ThematicGrouping<Date> groupingDate = getGrouping( dataDate, (Integer) numberOfClassesSpinner.getValue() ); tableModelDate.setThematicGrouping( groupingDate, getGroupingType() ); configureClassesTable( tableModelDate, groupingDate.getAttributeHeader(), new ClassificationValuesRenderer<Date>(), new ClassificationValuesEditor<Date>( new DateIntervallable( new Date(), datePattern ) ) ); break; case Types.VARCHAR: default: ClassificationTableModel<String> tableModelString = new ClassificationTableModel<String>( getColumns(), assignedVisualPropPanel.getOwner() ); List<Intervallable<String>> dataString = new ArrayList<Intervallable<String>>(); for ( Object o : pv.getValues() ) { dataString.add( new StringIntervallable( (String) o ) ); } ThematicGrouping<String> groupingString = getGrouping( dataString, (Integer) numberOfClassesSpinner.getValue() ); tableModelString.setThematicGrouping( groupingString, getGroupingType() ); configureClassesTable( tableModelString, groupingString.getAttributeHeader(), new ClassificationValuesRenderer<String>(), new ClassificationValuesEditor<String>( new StringIntervallable( "dummy" ) ) ); break; } if ( classificationTypeCB.getSelectedItem() == uniqueValue || classificationTypeCB.getSelectedItem() == quality ) { getModel().getThematicGrouping().setFillColor( new RandomColors() ); model.update( FILLCOLOR, true ); } model.update( VALUE, true ); } } private GROUPINGTYPE getGroupingType() { if ( classificationTypeCB.getSelectedItem() == equalInterval ) { return EQUAL; } else if ( classificationTypeCB.getSelectedItem() == quantile ) { return QUANTILE; } else if ( classificationTypeCB.getSelectedItem() == quality ) { return QUALITY; } else if ( classificationTypeCB.getSelectedItem() == uniqueValue ) { return UNIQUE; } return GROUPINGTYPE.MANUAL; } private <V extends Comparable<V>> ThematicGrouping<V> getGrouping( List<Intervallable<V>> data, int noOfClasses ) { ThematicGrouping<V> grouping; if ( classificationTypeCB.getSelectedItem() == equalInterval ) { grouping = new EqualIntervalClassification<V>(); } else if ( classificationTypeCB.getSelectedItem() == quantile ) { grouping = new QuantileClassification<V>(); } else if ( classificationTypeCB.getSelectedItem() == quality ) { grouping = new QualityClassification<V>(); } else { grouping = new UniqueValueGrouping<V>(); } grouping.setData( data ); grouping.setNoOfClasses( noOfClasses ); return grouping; } private JPanel buildTableChangerButtonBar() { ButtonBarBuilder bbBuilder = new ButtonBarBuilder(); bbBuilder.addGriddedButtons( new JButton[] { addRowBt, removeRowBt } ); return bbBuilder.getPanel(); } private void fillPropertyCB( List<QualifiedName> properties ) { QualifiedName selectedQn = null; if ( propertyCB.getSelectedItem() != null ) { selectedQn = (QualifiedName) propertyCB.getSelectedItem(); } propertyCB.removeAllItems(); for ( QualifiedName qn : properties ) { propertyCB.addItem( qn ); if ( selectedQn != null && qn.equals( selectedQn ) ) { propertyCB.setSelectedItem( qn ); } } } private void updateAfterClassificationChanged() { List<QualifiedName> pvList; boolean isClassesSpinnerEnabled = false; if ( classificationTypeCB.getSelectedItem() == equalInterval ) { pvList = assignedVisualPropPanel.getOwner().getPropertyNames( Types.FLOAT, Types.DOUBLE, Types.INTEGER, Types.DATE, Types.BIGINT, Types.SMALLINT ); isClassesSpinnerEnabled = true; numberOfClassesSpinner.setValue( 6 ); } else if ( classificationTypeCB.getSelectedItem() == quantile ) { pvList = assignedVisualPropPanel.getOwner().getPropertyNames( Types.FLOAT, Types.DOUBLE, Types.INTEGER, Types.DATE, Types.BIGINT, Types.SMALLINT ); isClassesSpinnerEnabled = true; numberOfClassesSpinner.setValue( 6 ); } else { pvList = assignedVisualPropPanel.getOwner().getPropertyNames(); } isUpdating = true; fillPropertyCB( pvList ); isUpdating = false; numberOfClassesSpinner.setEnabled( isClassesSpinnerEnabled ); updateTable(); numberOfClassesSpinner.setValue( model.getClassification().size() ); initColumnValues(); } private void setClassificationActive() { if ( !isInitialising ) { status.setSelected( true ); assignedVisualPropPanel.setActive( true ); } } private void openHistogram() { if ( propertyCB.getSelectedItem() != null ) { String title = get( "$MD11050", ( (QualifiedName) propertyCB.getSelectedItem() ).getLocalName() ); List<ValueRange<?>> values = new ArrayList<ValueRange<?>>(); int dataSize = this.model.getThematicGrouping().getNumberOfData(); int classes = this.model.getRowCount(); if ( dataSize / classes > 2 ) { for ( ClassificationTableRow<?> row : this.model.getRows() ) { values.add( row.getValue() ); } histogram.update( title, values ); openHistogramBt.setText( get( "$MD11504" ) ); } else { JOptionPane.showMessageDialog( this, get( "$MD11530", dataSize, classes, 2 ), get( "$MD11529" ), JOptionPane.INFORMATION_MESSAGE ); } } else { JOptionPane.showMessageDialog( this, get( "$MD11051" ), get( "$DI10018" ), JOptionPane.INFORMATION_MESSAGE ); } } private void addManuellClassificationItem() { if ( classificationTypeCB.getSelectedItem() != uniqueValue && ( ( classificationTypeCB.getItemCount() == 0 ) || !isManual.isVisible() ) ) { isManual.setVisible( true ); numberOfClassesSpinner.setEnabled( false ); propertyCB.setEnabled( false ); } } private void removeManuellClassificationItem() { isManual.setVisible( false ); numberOfClassesSpinner.setEnabled( true ); propertyCB.setEnabled( true ); } /** * transfers the values from the single elmements of the style to the classification */ abstract public void initColumnValues(); abstract protected List<Column> getColumns(); abstract protected SYMBOLIZERTYPE getSymbolizerType(); // ////////////////////////////////////////////////////////////////////////////// // ACTIONLISTENER // ////////////////////////////////////////////////////////////////////////////// /* * (non-Javadoc) * * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent) */ public void actionPerformed( ActionEvent e ) { Object src = e.getSource(); if ( src == classificationTypeCB ) { if ( isManual.isVisible() ) { removeManuellClassificationItem(); } unknownClassification.setEnabled( false ); updateAfterClassificationChanged(); setClassificationActive(); } else if ( src == propertyCB ) { if ( this.propertyCB.getSelectedItem() != null && !isUpdating ) { updateTable(); } setClassificationActive(); } else if ( src == addRowBt ) { int row = classesTable.getSelectedRow(); if ( row < 0 ) { row = model.getRowCount(); } model.addRowBefore( row ); numberOfClassesSpinner.setValue( classesTable.getRowCount() ); setClassificationActive(); } else if ( src == removeRowBt ) { // stop cell editing before removing selected rows int editingRow = classesTable.getEditingRow(); int editingColumn = classesTable.getEditingColumn(); if ( editingRow > -1 && editingColumn > -1 && classesTable.getCellEditor( editingRow, editingColumn ) != null ) { classesTable.getCellEditor( editingRow, editingColumn ).cancelCellEditing(); } model.removeRows( classesTable.getSelectedRows() ); numberOfClassesSpinner.setValue( classesTable.getRowCount() ); setClassificationActive(); } else if ( src == openHistogramBt ) { openHistogram(); } else if ( src == fullDatabase || src == extentDatabase ) { updateTable(); } } // ////////////////////////////////////////////////////////////////////////////// // TABLEMODELLISTENER // ////////////////////////////////////////////////////////////////////////////// public void tableChanged( TableModelEvent e ) { // mark as manuell classification, if user changed a class limit in the table or // inserted/removed a row if ( ( model.getColumnIndex( VALUE ) == e.getColumn() && !isManual.isVisible() ) || ( e.getType() == TableModelEvent.INSERT ) || ( e.getType() == TableModelEvent.DELETE ) ) { addManuellClassificationItem(); } } // ////////////////////////////////////////////////////////////////////////////// // inner classes // ////////////////////////////////////////////////////////////////////////////// private class ClassesTable extends JTable { private static final long serialVersionUID = 8303943959907931881L; @Override public TableCellRenderer getCellRenderer( int row, int column ) { switch ( model.getColumnType( column ) ) { case FILLCOLOR: case LINECOLOR: case FONTCOLOR: case HALOCOLOR: return new ColorTableCellRenderer(); case SYMBOL: return new SymbolTableCellRenderer(); case LINECAP: return new SldPropertyCellRenderer(); default: return new LocaleTableCellRenderer(); } } @Override public TableCellEditor getCellEditor( int row, int column ) { TableCellEditor editor; switch ( model.getColumnType( column ) ) { case FILLCOLOR: editor = new FillTableCellEditor( assignedVisualPropPanel.getOwner().getSettings().getGraphicOptions(), SldValues.getDefaultColor() ); break; case LINECOLOR: case HALOCOLOR: case FONTCOLOR: editor = new ColorTableCellEditor(); break; case FILLTRANSPARENCY: case LINETRANSPARENCY: case FONTTRANSPARENCY: int opAsInt = SldValues.getOpacityInPercent( SldValues.getDefaultOpacity() ); editor = new SpinnerTableCellEditor( opAsInt, 0, 100, 1 ); break; case LINEWIDTH: editor = new SpinnerTableCellEditor( SldValues.getDefaultLineWidth(), 0.0, Integer.MAX_VALUE, 0.5 ); break; case LINESTYLE: JComboBox lineStyleCB = new JComboBox(); int lineStyleCol = model.getColumnIndex( LINESTYLE ); lineStyleCB.setRenderer( new DashArrayRenderer( getTableHeader().getColumnModel().getColumn( lineStyleCol ).getWidth(), 15 ) ); for ( DashArray da : SldValues.getDashArrays() ) { lineStyleCB.addItem( da ); } Map<String, DashArray> dashArrays = assignedVisualPropPanel.getOwner().getSettings().getGraphicOptions().getDashArrays(); for ( DashArray da : dashArrays.values() ) { lineStyleCB.addItem( da ); } editor = new DefaultCellEditor( lineStyleCB ); break; case SIZE: editor = new SpinnerTableCellEditor( SldValues.getDefaultSize(), 0.00001, Integer.MAX_VALUE, 1.0 ); break; case SYMBOL: JComboBox symbolCB = new JComboBox(); symbolCB.setRenderer( new SymbolRenderer() ); for ( WellKnownMark mark : SldValues.getWellKnownMarks() ) { symbolCB.addItem( mark ); } try { Map<String, GraphicSymbol> symbols = assignedVisualPropPanel.getOwner().getSettings().getGraphicOptions().getSymbolDefinitions(); List<GraphicSymbol> values = new ArrayList<GraphicSymbol>(); values.addAll( symbols.values() ); Collections.sort( (List<GraphicSymbol>) values ); for ( GraphicSymbol symbol : values ) { symbolCB.addItem( symbol ); } } catch ( MalformedURLException e ) { JOptionPane.showMessageDialog( this, get( "$MD10788" ), get( "$DI10017" ), JOptionPane.ERROR_MESSAGE ); } editor = new DefaultCellEditor( symbolCB ); break; case LINECAP: JComboBox lineCapCB = new JComboBox(); lineCapCB.setRenderer( new SldPropertyRenderer() ); for ( SldProperty lc : SldValues.getLineCaps() ) { lineCapCB.addItem( lc ); } editor = new DefaultCellEditor( lineCapCB ); break; case FONTFAMILY: FontHelper fhf = new FontHelper(); editor = new DefaultCellEditor( fhf.createFontFamilyChooser() ); break; case FONTSTYLE: FontHelper fhs = new FontHelper(); editor = new DefaultCellEditor( fhs.createFontStyleChooser() ); break; case FONTWEIGHT: FontHelper fhw = new FontHelper(); editor = new DefaultCellEditor( fhw.createFontWeightChooser() ); break; case FONTSIZE: editor = new SpinnerTableCellEditor( SldValues.getDefaultFontSize(), 1.0, Double.MAX_VALUE, 1.0 ); break; case HALORADIUS: editor = new SpinnerTableCellEditor( SldValues.getDefaultHaloRadius(), 0d, 50d, 1d ); break; case ROTATION: editor = new SpinnerTableCellEditor( SldValues.getDefaultRotation(), 0.0, 360.0, 5.0d ); break; case DISPLACEMENT: editor = new PointCellEditor( SldValues.getDefaultDisplacement() ); break; case ANCHORPOINT: editor = new PointCellEditor( SldValues.getDefaultAnchorPoint() ); break; default: return super.getCellEditor( row, column ); } // add cell editor listener, to be informed, when cell editing was stopped or // canceled; was necessary, because of problems after deleting an editing row! editor.addCellEditorListener( this ); return editor; } // Implement table header to show tool tips. @Override protected JTableHeader createDefaultTableHeader() { return new JTableHeader( columnModel ) { private static final long serialVersionUID = 7937684822965281600L; public String getToolTipText( MouseEvent e ) { java.awt.Point p = e.getPoint(); int index = columnModel.getColumnIndexAtX( p.x ); int realIndex = columnModel.getColumn( index ).getModelIndex(); return model.getColumnTooltip( realIndex ); } }; } }; }