/* * 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; import java.awt.event.ComponentAdapter; import java.awt.event.ComponentEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseMotionAdapter; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.Iterator; import javax.swing.BorderFactory; import javax.swing.Icon; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JSplitPane; import javax.swing.JTable; import javax.swing.ListSelectionModel; import javax.swing.SwingConstants; import javax.swing.SwingUtilities; import javax.swing.border.Border; import javax.swing.event.ListSelectionListener; import javax.swing.event.TableModelEvent; import javax.swing.event.TableModelListener; import javax.swing.table.DefaultTableCellRenderer; import javax.swing.table.JTableHeader; import javax.swing.table.TableCellEditor; import javax.swing.table.TableCellRenderer; import javax.swing.table.TableColumn; import com.vividsolutions.jump.I18N; import com.vividsolutions.jts.geom.*; import com.vividsolutions.jump.feature.Feature; import com.vividsolutions.jump.util.FlexibleDateParser; import com.vividsolutions.jump.workbench.WorkbenchContext; import com.vividsolutions.jump.workbench.model.CategoryEvent; import com.vividsolutions.jump.workbench.model.FeatureEvent; import com.vividsolutions.jump.workbench.model.Layer; import com.vividsolutions.jump.workbench.model.LayerEvent; import com.vividsolutions.jump.workbench.model.LayerEventType; import com.vividsolutions.jump.workbench.model.LayerListener; import com.vividsolutions.jump.workbench.plugin.PlugIn; import com.vividsolutions.jump.workbench.plugin.PlugInContext; import com.vividsolutions.jump.workbench.ui.ColumnBasedTableModel.Column; import com.vividsolutions.jump.workbench.ui.images.IconLoader; import com.vividsolutions.jump.workbench.ui.plugin.EditSelectedFeaturePlugIn; import java.awt.*; /** * Implements an AttributeTable panel. Table-size changes are absorbed by the * last column. Rows are striped for non-editable table. */ public class AttributeTablePanel extends JPanel { public static interface FeatureEditor { void edit(PlugInContext context, Feature feature, Layer layer) throws Exception; } private FeatureEditor featureEditor = new FeatureEditor() { public void edit(PlugInContext context, Feature feature, final Layer myLayer) throws Exception { new EditSelectedFeaturePlugIn() { protected Layer layer(PlugInContext context) { //Hopefully nobody will ever delete or rename the // superclass' #layer method. //[Jon Aquino] return myLayer; //Name "myLayer" because we don't want the //superclass' "layer" [Jon Aquino 2004-03-17] } }.execute(context, feature, myLayer.isEditable()); } }; private GridBagLayout gridBagLayout1 = new GridBagLayout(); private class MyTable extends JTable { public MyTable() { //We want table-size changes to be absorbed by the last column. //By default, AUTO_RESIZE_LAST_COLUMN will not achieve this //(it works for column-size changes only). But I am overriding //#sizeColumnsToFit (for J2SE 1.3) and //JTableHeader#getResizingColumn (for J2SE 1.4) //#so that it will work for table-size changes. [Jon Aquino] setAutoResizeMode(JTable.AUTO_RESIZE_OFF); GUIUtil.doNotRoundDoubles(this); setDefaultEditor(Date.class, new FlexibleDateParser.CellEditor()); } //Row-stripe colour recommended in //Java Look and Feel Design Guidelines: Advanced Topics [Jon Aquino] private final Color LIGHT_GRAY = new Color(230, 230, 230); private GeometryCellRenderer geomCellRenderer = new GeometryCellRenderer(); public TableCellRenderer getCellRenderer(int row, int column) { if (!isEditButtonColumn(column)) { JComponent renderer = (JComponent) super.getCellRenderer(row, column); if (AttributeTablePanel.this.getModel().getLayer().isEditable() && !AttributeTablePanel.this.getModel() .isCellEditable(row, column)) // Shade readonly cells light gray renderer.setBackground(LIGHT_GRAY); else { // If not editable, use row striping, as recommended in // Java Look and Feel Design Guidelines: Advanced Topics // [Jon Aquino] renderer.setBackground((AttributeTablePanel.this.getModel() .getLayer().isEditable() || ((row % 2) == 0)) ? Color.white : LIGHT_GRAY); } return (TableCellRenderer) renderer; } return geomCellRenderer; } }; private class GeometryCellRenderer implements TableCellRenderer { private JButton button = new JButton(IconLoader.icon("Pencil.gif")); private JButton buttonPoint = new JButton(IconLoader.icon("EditPoint.gif")); private JButton buttonMultiPoint = new JButton(IconLoader.icon("EditMultiPoint.gif")); private JButton buttonLineString = new JButton(IconLoader.icon("EditLineString.gif")); private JButton buttonMultiLineString = new JButton(IconLoader.icon("EditMultiLineString.gif")); private JButton buttonPolygon = new JButton(IconLoader.icon("EditPolygon.gif")); private JButton buttonMultiPolygon = new JButton(IconLoader.icon("EditMultiPolygon.gif")); private JButton buttonGC = new JButton(IconLoader.icon("EditGeometryCollection.gif")); private JButton buttonEmptyGC = new JButton(IconLoader.icon("EditEmptyGC.gif")); GeometryCellRenderer() { buttonPoint.setToolTipText("View/Edit Point"); buttonMultiPoint.setToolTipText("View/Edit MultiPoint"); buttonLineString.setToolTipText("View/Edit LineString"); buttonMultiLineString.setToolTipText("View/Edit MultiLineString"); buttonPolygon.setToolTipText("View/Edit Polygon"); buttonMultiPolygon.setToolTipText("View/Edit MultiPolygon"); buttonGC.setToolTipText("View/Edit GeometryCollection"); buttonEmptyGC.setToolTipText("View/Edit empty GeometryCollection"); button.setToolTipText("View/Edit Geometry"); } public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { Feature f = (Feature) value; Geometry g = f.getGeometry(); if (g instanceof com.vividsolutions.jts.geom.Point) return buttonPoint; if (g instanceof com.vividsolutions.jts.geom.MultiPoint) return buttonMultiPoint; if (g instanceof com.vividsolutions.jts.geom.LineString) return buttonLineString; if (g instanceof com.vividsolutions.jts.geom.MultiLineString) return buttonMultiLineString; if (g instanceof com.vividsolutions.jts.geom.Polygon) return buttonPolygon; if (g instanceof com.vividsolutions.jts.geom.MultiPolygon) return buttonMultiPolygon; if (g.isEmpty()) return buttonEmptyGC; return buttonGC; } } private boolean columnWidthsInitialized = false; private MyTable table = new MyTable(); private TableCellRenderer headerRenderer = new TableCellRenderer() { private Icon clearIcon = IconLoader.icon("Clear.gif"); private Icon downIcon = IconLoader.icon("Down.gif"); private TableCellRenderer originalRenderer = table.getTableHeader() .getDefaultRenderer(); private Icon upIcon = IconLoader.icon("Up.gif"); public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { JLabel label = (JLabel) originalRenderer .getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); if ((getModel().getSortedColumnName() == null) || !getModel().getSortedColumnName().equals( table.getColumnName(column))) { label.setIcon(clearIcon); } else if (getModel().isSortAscending()) { label.setIcon(upIcon); } else { label.setIcon(downIcon); } label.setHorizontalTextPosition(SwingConstants.LEFT); return label; } }; private LayerNameRenderer layerNameRenderer = new LayerNameRenderer(); private ArrayList listeners = new ArrayList(); private WorkbenchContext workbenchContext; public AttributeTablePanel(final LayerTableModel model, boolean addScrollPane, final WorkbenchContext workbenchContext) { this(); if (addScrollPane) { remove(table); remove(table.getTableHeader()); JScrollPane scrollPane = new JScrollPane(); scrollPane.getViewport().add(table); this.add(scrollPane, new GridBagConstraints(0, 2, 1, 1, 1, 1, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets( 0, 0, 0, 0), 0, 0)); } updateGrid(model.getLayer()); model.getLayer().getLayerManager().addLayerListener( new LayerListener() { public void categoryChanged(CategoryEvent e) { } public void featuresChanged(FeatureEvent e) { } public void layerChanged(LayerEvent e) { if (e.getLayerable() != model.getLayer()) { return; } if (e.getType() == LayerEventType.METADATA_CHANGED) { //If layer becomes editable, apply row striping // and remove gridlines, //as recommended in Java Look and Feel Design // Guidelines: Advanced Topics [Jon Aquino] updateGrid(model.getLayer()); repaint(); } } }); try { JList list = new JList(); list.setBackground(new JLabel().getBackground()); layerNameRenderer.getListCellRendererComponent(list, model .getLayer(), -1, false, false); table.setModel(model); model.addTableModelListener(new TableModelListener() { public void tableChanged(TableModelEvent e) { if (e.getFirstRow() == TableModelEvent.HEADER_ROW) { //Structure changed (LayerTableModel specifies // HEADER_ROW). //Add this listener after the table adds its listeners //(in table.setModel above) so that this listener will // initialize the column //widths after the table re-adds the columns. [Jon // Aquino] initColumnWidths(); } } }); layerNameRenderer.getLabel().setFont( layerNameRenderer.getLabel().getFont() .deriveFont(Font.BOLD)); model.addTableModelListener(new TableModelListener() { public void tableChanged(TableModelEvent e) { updateLabel(); } }); updateLabel(); this.workbenchContext = workbenchContext; table.setSelectionModel(new SelectionModelWrapper(this)); table.getTableHeader().setDefaultRenderer(headerRenderer); initColumnWidths(); setToolTips(); setBorder(BorderFactory.createMatteBorder(0, 5, 0, 0, new FeatureInfoWriter().sidebarColor(model.getLayer()))); table.getTableHeader().addMouseListener(new MouseAdapter() { public void mouseClicked(MouseEvent e) { try { int column = table.columnAtPoint(e.getPoint()); if (isEditButtonColumn(column)) { return; } if (SwingUtilities.isLeftMouseButton(e)) { model.sort(table.getColumnName(column)); } } catch (Throwable t) { workbenchContext.getErrorHandler().handleThrowable(t); } } }); table.addMouseListener(new MouseAdapter() { public void mouseClicked(MouseEvent e) { try { int column = table.columnAtPoint(e.getPoint()); int row = table.rowAtPoint(e.getPoint()); if (isEditButtonColumn(column)) { PlugInContext context = new PlugInContext( workbenchContext, null, model.getLayer(), null, null); model.getLayer().getLayerManager() .getUndoableEditReceiver().startReceiving(); try { featureEditor.edit(context, model .getFeature(row), model.getLayer()); } finally { model.getLayer().getLayerManager() .getUndoableEditReceiver() .stopReceiving(); } return; } } catch (Throwable t) { workbenchContext.getErrorHandler().handleThrowable(t); } } }); } catch (Throwable t) { workbenchContext.getErrorHandler().handleThrowable(t); } } private AttributeTablePanel() { try { jbInit(); } catch (Exception e) { throw new RuntimeException(e); } } private void updateGrid(Layer layer) { table.setShowGrid(layer.isEditable()); } private boolean isEditButtonColumn(int column) { return getModel().getColumnName(0).equals(table.getColumnName(column)); } private void updateLabel() {//[sstein] change for translation if (getModel().getRowCount() == 1) { layerNameRenderer.getLabel().setText( getModel().getLayer().getName() + " (" + getModel().getRowCount() + " " + I18N.get("ui.AttributeTablePanel.feature") + ")"); } else { layerNameRenderer.getLabel().setText( getModel().getLayer().getName() + " (" + getModel().getRowCount() + " " + I18N.get("ui.AttributeTablePanel.features") + ")"); } } public LayerTableModel getModel() { return (LayerTableModel) table.getModel(); } public JTable getTable() { return table; } public void addListener(AttributeTablePanelListener listener) { listeners.add(listener); } void jbInit() throws Exception { this.setLayout(gridBagLayout1); this.add(layerNameRenderer, new GridBagConstraints(0, 0, 2, 1, 1.0, 0.0, GridBagConstraints.NORTHWEST, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0)); this.add(table.getTableHeader(), new GridBagConstraints(0, 1, 1, 1, 0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0)); //Pad table on the right with 200 pixels so that user has some space //in which to resize last column. [Jon Aquino] this.add(table, new GridBagConstraints(0, 2, 1, 1, 0, 0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets( 0, 0, 0, 200), 0, 0)); } private void initColumnWidths() { GUIUtil.chooseGoodColumnWidths(table); int editButtonWidth = 16; table.getColumnModel().getColumn(0).setMinWidth(editButtonWidth); table.getColumnModel().getColumn(0).setMaxWidth(editButtonWidth); table.getColumnModel().getColumn(0).setPreferredWidth(editButtonWidth); columnWidthsInitialized = true; } private void setToolTips() { table.addMouseMotionListener(new MouseMotionAdapter() { public void mouseMoved(MouseEvent e) { int column = table.columnAtPoint(e.getPoint()); if (column == -1) { return; } table.setToolTipText(table.getColumnName(column) + " [" + getModel().getLayer().getName() + "]"); } }); } /** * Called when the user creates a new selection, rather than adding to the * existing selection */ private void fireSelectionReplaced() { for (Iterator i = listeners.iterator(); i.hasNext();) { AttributeTablePanelListener listener = (AttributeTablePanelListener) i .next(); listener.selectionReplaced(this); } } private static class SelectionModelWrapper implements ListSelectionModel { private AttributeTablePanel panel; private ListSelectionModel selectionModel; public SelectionModelWrapper(AttributeTablePanel panel) { this.panel = panel; selectionModel = panel.table.getSelectionModel(); } public void setAnchorSelectionIndex(int index) { selectionModel.setAnchorSelectionIndex(index); } public void setLeadSelectionIndex(int index) { selectionModel.setLeadSelectionIndex(index); } public void setSelectionInterval(int index0, int index1) { selectionModel.setSelectionInterval(index0, index1); panel.fireSelectionReplaced(); } public void setSelectionMode(int selectionMode) { selectionModel.setSelectionMode(selectionMode); } public void setValueIsAdjusting(boolean valueIsAdjusting) { selectionModel.setValueIsAdjusting(valueIsAdjusting); } public int getAnchorSelectionIndex() { return selectionModel.getAnchorSelectionIndex(); } public int getLeadSelectionIndex() { return selectionModel.getLeadSelectionIndex(); } public int getMaxSelectionIndex() { return selectionModel.getMaxSelectionIndex(); } public int getMinSelectionIndex() { return selectionModel.getMinSelectionIndex(); } public int getSelectionMode() { return selectionModel.getSelectionMode(); } public boolean getValueIsAdjusting() { return selectionModel.getValueIsAdjusting(); } public boolean isSelectedIndex(int index) { return selectionModel.isSelectedIndex(index); } public boolean isSelectionEmpty() { return selectionModel.isSelectionEmpty(); } public void addListSelectionListener(ListSelectionListener x) { selectionModel.addListSelectionListener(x); } public void addSelectionInterval(int index0, int index1) { selectionModel.addSelectionInterval(index0, index1); } public void clearSelection() { selectionModel.clearSelection(); } public void insertIndexInterval(int index, int length, boolean before) { selectionModel.insertIndexInterval(index, length, before); } public void removeIndexInterval(int index0, int index1) { selectionModel.removeIndexInterval(index0, index1); } public void removeListSelectionListener(ListSelectionListener x) { selectionModel.removeListSelectionListener(x); } public void removeSelectionInterval(int index0, int index1) { selectionModel.removeSelectionInterval(index0, index1); } } public Collection getSelectedFeatures() { ArrayList selectedFeatures = new ArrayList(); if (getModel().getRowCount() == 0) { return selectedFeatures; } int[] selectedRows = table.getSelectedRows(); for (int i = 0; i < selectedRows.length; i++) { selectedFeatures.add(getModel().getFeature(selectedRows[i])); } return selectedFeatures; } public LayerNameRenderer getLayerNameRenderer() { return layerNameRenderer; } public void setFeatureEditor(FeatureEditor featureEditor) { this.featureEditor = featureEditor; } }