001: /*
002: * The Unified Mapping Platform (JUMP) is an extensible, interactive GUI for
003: * visualizing and manipulating spatial features with geometry and attributes.
004: *
005: * Copyright (C) 2003 Vivid Solutions
006: *
007: * This program is free software; you can redistribute it and/or modify it
008: * under the terms of the GNU General Public License as published by the Free
009: * Software Foundation; either version 2 of the License, or (at your option)
010: * any later version.
011: *
012: * This program is distributed in the hope that it will be useful, but WITHOUT
013: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
014: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
015: * more details.
016: *
017: * You should have received a copy of the GNU General Public License along with
018: * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
019: * Place - Suite 330, Boston, MA 02111-1307, USA.
020: *
021: * For more information, contact:
022: *
023: * Vivid Solutions Suite #1A 2328 Government Street Victoria BC V8T 5G5 Canada
024: *
025: * (250)385-6040 www.vividsolutions.com
026: */
027: package com.vividsolutions.jump.workbench.ui;
028:
029: import java.awt.event.ComponentAdapter;
030: import java.awt.event.ComponentEvent;
031: import java.awt.event.MouseAdapter;
032: import java.awt.event.MouseEvent;
033: import java.awt.event.MouseMotionAdapter;
034: import java.util.ArrayList;
035: import java.util.Collection;
036: import java.util.Date;
037: import java.util.Iterator;
038: import javax.swing.BorderFactory;
039: import javax.swing.Icon;
040: import javax.swing.JButton;
041: import javax.swing.JComponent;
042: import javax.swing.JLabel;
043: import javax.swing.JList;
044: import javax.swing.JPanel;
045: import javax.swing.JScrollPane;
046: import javax.swing.JSplitPane;
047: import javax.swing.JTable;
048: import javax.swing.ListSelectionModel;
049: import javax.swing.SwingConstants;
050: import javax.swing.SwingUtilities;
051: import javax.swing.border.Border;
052: import javax.swing.event.ListSelectionListener;
053: import javax.swing.event.TableModelEvent;
054: import javax.swing.event.TableModelListener;
055: import javax.swing.table.DefaultTableCellRenderer;
056: import javax.swing.table.JTableHeader;
057: import javax.swing.table.TableCellEditor;
058: import javax.swing.table.TableCellRenderer;
059: import javax.swing.table.TableColumn;
060:
061: import com.vividsolutions.jump.I18N;
062: import com.vividsolutions.jts.geom.*;
063: import com.vividsolutions.jump.feature.Feature;
064: import com.vividsolutions.jump.util.FlexibleDateParser;
065: import com.vividsolutions.jump.workbench.WorkbenchContext;
066: import com.vividsolutions.jump.workbench.model.CategoryEvent;
067: import com.vividsolutions.jump.workbench.model.FeatureEvent;
068: import com.vividsolutions.jump.workbench.model.Layer;
069: import com.vividsolutions.jump.workbench.model.LayerEvent;
070: import com.vividsolutions.jump.workbench.model.LayerEventType;
071: import com.vividsolutions.jump.workbench.model.LayerListener;
072: import com.vividsolutions.jump.workbench.plugin.PlugIn;
073: import com.vividsolutions.jump.workbench.plugin.PlugInContext;
074: import com.vividsolutions.jump.workbench.ui.ColumnBasedTableModel.Column;
075: import com.vividsolutions.jump.workbench.ui.images.IconLoader;
076: import com.vividsolutions.jump.workbench.ui.plugin.EditSelectedFeaturePlugIn;
077: import java.awt.*;
078:
079: /**
080: * Implements an AttributeTable panel. Table-size changes are absorbed by the
081: * last column. Rows are striped for non-editable table.
082: */
083:
084: public class AttributeTablePanel extends JPanel {
085:
086: public static interface FeatureEditor {
087:
088: void edit(PlugInContext context, Feature feature, Layer layer)
089: throws Exception;
090:
091: }
092:
093: private FeatureEditor featureEditor = new FeatureEditor() {
094:
095: public void edit(PlugInContext context, Feature feature,
096: final Layer myLayer) throws Exception {
097: new EditSelectedFeaturePlugIn() {
098:
099: protected Layer layer(PlugInContext context) {
100: //Hopefully nobody will ever delete or rename the
101: // superclass' #layer method.
102: //[Jon Aquino]
103: return myLayer;
104: //Name "myLayer" because we don't want the
105: //superclass' "layer" [Jon Aquino 2004-03-17]
106: }
107: }.execute(context, feature, myLayer.isEditable());
108: }
109: };
110:
111: private GridBagLayout gridBagLayout1 = new GridBagLayout();
112:
113: private class MyTable extends JTable {
114:
115: public MyTable() {
116: //We want table-size changes to be absorbed by the last column.
117: //By default, AUTO_RESIZE_LAST_COLUMN will not achieve this
118: //(it works for column-size changes only). But I am overriding
119: //#sizeColumnsToFit (for J2SE 1.3) and
120: //JTableHeader#getResizingColumn (for J2SE 1.4)
121: //#so that it will work for table-size changes. [Jon Aquino]
122: setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
123: GUIUtil.doNotRoundDoubles(this );
124: setDefaultEditor(Date.class,
125: new FlexibleDateParser.CellEditor());
126: }
127:
128: //Row-stripe colour recommended in
129: //Java Look and Feel Design Guidelines: Advanced Topics [Jon Aquino]
130: private final Color LIGHT_GRAY = new Color(230, 230, 230);
131:
132: private GeometryCellRenderer geomCellRenderer = new GeometryCellRenderer();
133:
134: public TableCellRenderer getCellRenderer(int row, int column) {
135: if (!isEditButtonColumn(column)) {
136: JComponent renderer = (JComponent) super
137: .getCellRenderer(row, column);
138: //If not editable, use row striping, as recommended in
139: //Java Look and Feel Design Guidelines: Advanced Topics [Jon
140: // Aquino]
141: renderer
142: .setBackground((AttributeTablePanel.this
143: .getModel().getLayer().isEditable() || ((row % 2) == 0)) ? Color.white
144: : LIGHT_GRAY);
145: return (TableCellRenderer) renderer;
146: }
147: return geomCellRenderer;
148: }
149: };
150:
151: private class GeometryCellRenderer implements TableCellRenderer {
152: private JButton button = new JButton(IconLoader
153: .icon("Pencil.gif"));
154: private JButton buttonPoint = new JButton(IconLoader
155: .icon("EditPoint.gif"));
156: private JButton buttonMultiPoint = new JButton(IconLoader
157: .icon("EditMultiPoint.gif"));
158: private JButton buttonLineString = new JButton(IconLoader
159: .icon("EditLineString.gif"));
160: private JButton buttonMultiLineString = new JButton(IconLoader
161: .icon("EditMultiLineString.gif"));
162: private JButton buttonPolygon = new JButton(IconLoader
163: .icon("EditPolygon.gif"));
164: private JButton buttonMultiPolygon = new JButton(IconLoader
165: .icon("EditMultiPolygon.gif"));
166: private JButton buttonGC = new JButton(IconLoader
167: .icon("EditGeometryCollection.gif"));
168: private JButton buttonEmptyGC = new JButton(IconLoader
169: .icon("EditEmptyGC.gif"));
170:
171: GeometryCellRenderer() {
172: buttonPoint.setToolTipText("View/Edit Point");
173: buttonMultiPoint.setToolTipText("View/Edit MultiPoint");
174: buttonLineString.setToolTipText("View/Edit LineString");
175: buttonMultiLineString
176: .setToolTipText("View/Edit MultiLineString");
177: buttonPolygon.setToolTipText("View/Edit Polygon");
178: buttonMultiPolygon.setToolTipText("View/Edit MultiPolygon");
179: buttonGC.setToolTipText("View/Edit GeometryCollection");
180: buttonEmptyGC
181: .setToolTipText("View/Edit empty GeometryCollection");
182: button.setToolTipText("View/Edit Geometry");
183: }
184:
185: public Component getTableCellRendererComponent(JTable table,
186: Object value, boolean isSelected, boolean hasFocus,
187: int row, int column) {
188: Feature f = (Feature) value;
189: Geometry g = f.getGeometry();
190: if (g instanceof com.vividsolutions.jts.geom.Point)
191: return buttonPoint;
192: if (g instanceof com.vividsolutions.jts.geom.MultiPoint)
193: return buttonMultiPoint;
194: if (g instanceof com.vividsolutions.jts.geom.LineString)
195: return buttonLineString;
196: if (g instanceof com.vividsolutions.jts.geom.MultiLineString)
197: return buttonMultiLineString;
198: if (g instanceof com.vividsolutions.jts.geom.Polygon)
199: return buttonPolygon;
200: if (g instanceof com.vividsolutions.jts.geom.MultiPolygon)
201: return buttonMultiPolygon;
202: if (g.isEmpty())
203: return buttonEmptyGC;
204: return buttonGC;
205: }
206: }
207:
208: private boolean columnWidthsInitialized = false;
209:
210: private MyTable table = new MyTable();
211:
212: private TableCellRenderer headerRenderer = new TableCellRenderer() {
213:
214: private Icon clearIcon = IconLoader.icon("Clear.gif");
215:
216: private Icon downIcon = IconLoader.icon("Down.gif");
217:
218: private TableCellRenderer originalRenderer = table
219: .getTableHeader().getDefaultRenderer();
220:
221: private Icon upIcon = IconLoader.icon("Up.gif");
222:
223: public Component getTableCellRendererComponent(JTable table,
224: Object value, boolean isSelected, boolean hasFocus,
225: int row, int column) {
226: JLabel label = (JLabel) originalRenderer
227: .getTableCellRendererComponent(table, value,
228: isSelected, hasFocus, row, column);
229: if ((getModel().getSortedColumnName() == null)
230: || !getModel().getSortedColumnName().equals(
231: table.getColumnName(column))) {
232: label.setIcon(clearIcon);
233: } else if (getModel().isSortAscending()) {
234: label.setIcon(upIcon);
235: } else {
236: label.setIcon(downIcon);
237: }
238: label.setHorizontalTextPosition(SwingConstants.LEFT);
239: return label;
240: }
241: };
242:
243: private LayerNameRenderer layerNameRenderer = new LayerNameRenderer();
244:
245: private ArrayList listeners = new ArrayList();
246:
247: private WorkbenchContext workbenchContext;
248:
249: public AttributeTablePanel(final LayerTableModel model,
250: boolean addScrollPane,
251: final WorkbenchContext workbenchContext) {
252: this ();
253: if (addScrollPane) {
254: remove(table);
255: remove(table.getTableHeader());
256: JScrollPane scrollPane = new JScrollPane();
257: scrollPane.getViewport().add(table);
258: this .add(scrollPane, new GridBagConstraints(0, 2, 1, 1, 1,
259: 1, GridBagConstraints.CENTER,
260: GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0,
261: 0));
262: }
263: updateGrid(model.getLayer());
264: model.getLayer().getLayerManager().addLayerListener(
265: new LayerListener() {
266:
267: public void categoryChanged(CategoryEvent e) {
268: }
269:
270: public void featuresChanged(FeatureEvent e) {
271: }
272:
273: public void layerChanged(LayerEvent e) {
274: if (e.getLayerable() != model.getLayer()) {
275: return;
276: }
277: if (e.getType() == LayerEventType.METADATA_CHANGED) {
278: //If layer becomes editable, apply row striping
279: // and remove gridlines,
280: //as recommended in Java Look and Feel Design
281: // Guidelines: Advanced Topics [Jon Aquino]
282: updateGrid(model.getLayer());
283: repaint();
284: }
285: }
286: });
287: try {
288: JList list = new JList();
289: list.setBackground(new JLabel().getBackground());
290: layerNameRenderer.getListCellRendererComponent(list, model
291: .getLayer(), -1, false, false);
292: table.setModel(model);
293: model.addTableModelListener(new TableModelListener() {
294:
295: public void tableChanged(TableModelEvent e) {
296: if (e.getFirstRow() == TableModelEvent.HEADER_ROW) {
297: //Structure changed (LayerTableModel specifies
298: // HEADER_ROW).
299: //Add this listener after the table adds its listeners
300: //(in table.setModel above) so that this listener will
301: // initialize the column
302: //widths after the table re-adds the columns. [Jon
303: // Aquino]
304: initColumnWidths();
305: }
306: }
307: });
308: layerNameRenderer.getLabel().setFont(
309: layerNameRenderer.getLabel().getFont().deriveFont(
310: Font.BOLD));
311: model.addTableModelListener(new TableModelListener() {
312:
313: public void tableChanged(TableModelEvent e) {
314: updateLabel();
315: }
316: });
317: updateLabel();
318: this .workbenchContext = workbenchContext;
319: table.setSelectionModel(new SelectionModelWrapper(this ));
320: table.getTableHeader().setDefaultRenderer(headerRenderer);
321: initColumnWidths();
322: setToolTips();
323: setBorder(BorderFactory.createMatteBorder(0, 5, 0, 0,
324: new FeatureInfoWriter().sidebarColor(model
325: .getLayer())));
326: table.getTableHeader().addMouseListener(new MouseAdapter() {
327:
328: public void mouseClicked(MouseEvent e) {
329: try {
330: int column = table.columnAtPoint(e.getPoint());
331: if (isEditButtonColumn(column)) {
332: return;
333: }
334: if (SwingUtilities.isLeftMouseButton(e)) {
335: model.sort(table.getColumnName(column));
336: }
337: } catch (Throwable t) {
338: workbenchContext.getErrorHandler()
339: .handleThrowable(t);
340: }
341: }
342: });
343: table.addMouseListener(new MouseAdapter() {
344:
345: public void mouseClicked(MouseEvent e) {
346: try {
347: int column = table.columnAtPoint(e.getPoint());
348: int row = table.rowAtPoint(e.getPoint());
349: if (isEditButtonColumn(column)) {
350: PlugInContext context = new PlugInContext(
351: workbenchContext, null, model
352: .getLayer(), null, null);
353: model.getLayer().getLayerManager()
354: .getUndoableEditReceiver()
355: .startReceiving();
356: try {
357: featureEditor.edit(context, model
358: .getFeature(row), model
359: .getLayer());
360: } finally {
361: model.getLayer().getLayerManager()
362: .getUndoableEditReceiver()
363: .stopReceiving();
364: }
365: return;
366: }
367: } catch (Throwable t) {
368: workbenchContext.getErrorHandler()
369: .handleThrowable(t);
370: }
371: }
372: });
373: } catch (Throwable t) {
374: workbenchContext.getErrorHandler().handleThrowable(t);
375: }
376: }
377:
378: private AttributeTablePanel() {
379: try {
380: jbInit();
381: } catch (Exception e) {
382: throw new RuntimeException(e);
383: }
384: }
385:
386: private void updateGrid(Layer layer) {
387: table.setShowGrid(layer.isEditable());
388: }
389:
390: private boolean isEditButtonColumn(int column) {
391: return getModel().getColumnName(0).equals(
392: table.getColumnName(column));
393: }
394:
395: private void updateLabel() {//[sstein] change for translation
396: if (getModel().getRowCount() == 1) {
397: layerNameRenderer
398: .getLabel()
399: .setText(
400: getModel().getLayer().getName()
401: + " ("
402: + getModel().getRowCount()
403: + " "
404: + I18N
405: .get("ui.AttributeTablePanel.feature")
406: + ")");
407: } else {
408: layerNameRenderer
409: .getLabel()
410: .setText(
411: getModel().getLayer().getName()
412: + " ("
413: + getModel().getRowCount()
414: + " "
415: + I18N
416: .get("ui.AttributeTablePanel.features")
417: + ")");
418: }
419: }
420:
421: public LayerTableModel getModel() {
422: return (LayerTableModel) table.getModel();
423: }
424:
425: public JTable getTable() {
426: return table;
427: }
428:
429: public void addListener(AttributeTablePanelListener listener) {
430: listeners.add(listener);
431: }
432:
433: void jbInit() throws Exception {
434: this .setLayout(gridBagLayout1);
435: this .add(layerNameRenderer, new GridBagConstraints(0, 0, 2, 1,
436: 1.0, 0.0, GridBagConstraints.NORTHWEST,
437: GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0),
438: 0, 0));
439: this .add(table.getTableHeader(), new GridBagConstraints(0, 1,
440: 1, 1, 0, 0.0, GridBagConstraints.CENTER,
441: GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0),
442: 0, 0));
443: //Pad table on the right with 200 pixels so that user has some space
444: //in which to resize last column. [Jon Aquino]
445: this .add(table, new GridBagConstraints(0, 2, 1, 1, 0, 0,
446: GridBagConstraints.CENTER, GridBagConstraints.BOTH,
447: new Insets(0, 0, 0, 200), 0, 0));
448: }
449:
450: private void initColumnWidths() {
451: GUIUtil.chooseGoodColumnWidths(table);
452: int editButtonWidth = 16;
453: table.getColumnModel().getColumn(0)
454: .setMinWidth(editButtonWidth);
455: table.getColumnModel().getColumn(0)
456: .setMaxWidth(editButtonWidth);
457: table.getColumnModel().getColumn(0).setPreferredWidth(
458: editButtonWidth);
459: columnWidthsInitialized = true;
460: }
461:
462: private void setToolTips() {
463: table.addMouseMotionListener(new MouseMotionAdapter() {
464:
465: public void mouseMoved(MouseEvent e) {
466: int column = table.columnAtPoint(e.getPoint());
467: if (column == -1) {
468: return;
469: }
470: table.setToolTipText(table.getColumnName(column) + " ["
471: + getModel().getLayer().getName() + "]");
472: }
473: });
474: }
475:
476: /**
477: * Called when the user creates a new selection, rather than adding to the
478: * existing selection
479: */
480: private void fireSelectionReplaced() {
481: for (Iterator i = listeners.iterator(); i.hasNext();) {
482: AttributeTablePanelListener listener = (AttributeTablePanelListener) i
483: .next();
484: listener.selectionReplaced(this );
485: }
486: }
487:
488: private static class SelectionModelWrapper implements
489: ListSelectionModel {
490:
491: private AttributeTablePanel panel;
492:
493: private ListSelectionModel selectionModel;
494:
495: public SelectionModelWrapper(AttributeTablePanel panel) {
496: this .panel = panel;
497: selectionModel = panel.table.getSelectionModel();
498: }
499:
500: public void setAnchorSelectionIndex(int index) {
501: selectionModel.setAnchorSelectionIndex(index);
502: }
503:
504: public void setLeadSelectionIndex(int index) {
505: selectionModel.setLeadSelectionIndex(index);
506: }
507:
508: public void setSelectionInterval(int index0, int index1) {
509: selectionModel.setSelectionInterval(index0, index1);
510: panel.fireSelectionReplaced();
511: }
512:
513: public void setSelectionMode(int selectionMode) {
514: selectionModel.setSelectionMode(selectionMode);
515: }
516:
517: public void setValueIsAdjusting(boolean valueIsAdjusting) {
518: selectionModel.setValueIsAdjusting(valueIsAdjusting);
519: }
520:
521: public int getAnchorSelectionIndex() {
522: return selectionModel.getAnchorSelectionIndex();
523: }
524:
525: public int getLeadSelectionIndex() {
526: return selectionModel.getLeadSelectionIndex();
527: }
528:
529: public int getMaxSelectionIndex() {
530: return selectionModel.getMaxSelectionIndex();
531: }
532:
533: public int getMinSelectionIndex() {
534: return selectionModel.getMinSelectionIndex();
535: }
536:
537: public int getSelectionMode() {
538: return selectionModel.getSelectionMode();
539: }
540:
541: public boolean getValueIsAdjusting() {
542: return selectionModel.getValueIsAdjusting();
543: }
544:
545: public boolean isSelectedIndex(int index) {
546: return selectionModel.isSelectedIndex(index);
547: }
548:
549: public boolean isSelectionEmpty() {
550: return selectionModel.isSelectionEmpty();
551: }
552:
553: public void addListSelectionListener(ListSelectionListener x) {
554: selectionModel.addListSelectionListener(x);
555: }
556:
557: public void addSelectionInterval(int index0, int index1) {
558: selectionModel.addSelectionInterval(index0, index1);
559: }
560:
561: public void clearSelection() {
562: selectionModel.clearSelection();
563: }
564:
565: public void insertIndexInterval(int index, int length,
566: boolean before) {
567: selectionModel.insertIndexInterval(index, length, before);
568: }
569:
570: public void removeIndexInterval(int index0, int index1) {
571: selectionModel.removeIndexInterval(index0, index1);
572: }
573:
574: public void removeListSelectionListener(ListSelectionListener x) {
575: selectionModel.removeListSelectionListener(x);
576: }
577:
578: public void removeSelectionInterval(int index0, int index1) {
579: selectionModel.removeSelectionInterval(index0, index1);
580: }
581: }
582:
583: public Collection getSelectedFeatures() {
584: ArrayList selectedFeatures = new ArrayList();
585: if (getModel().getRowCount() == 0) {
586: return selectedFeatures;
587: }
588: int[] selectedRows = table.getSelectedRows();
589: for (int i = 0; i < selectedRows.length; i++) {
590: selectedFeatures
591: .add(getModel().getFeature(selectedRows[i]));
592: }
593: return selectedFeatures;
594: }
595:
596: public LayerNameRenderer getLayerNameRenderer() {
597: return layerNameRenderer;
598: }
599:
600: public void setFeatureEditor(FeatureEditor featureEditor) {
601: this.featureEditor = featureEditor;
602: }
603: }
|