001: /*
002: * The Unified Mapping Platform (JUMP) is an extensible, interactive GUI
003: * for 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
008: * modify it under the terms of the GNU General Public License
009: * as published by the Free Software Foundation; either version 2
010: * of the License, or (at your option) any later version.
011: *
012: * This program is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
015: * GNU General Public License for more details.
016: *
017: * You should have received a copy of the GNU General Public License
018: * along with this program; if not, write to the Free Software
019: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
020: *
021: * For more information, contact:
022: *
023: * Vivid Solutions
024: * Suite #1A
025: * 2328 Government Street
026: * Victoria BC V8T 5G5
027: * Canada
028: *
029: * (250)385-6040
030: * www.vividsolutions.com
031: */
032: package com.vividsolutions.jump.workbench.model;
033:
034: import java.awt.Color;
035: import java.util.*;
036:
037: import javax.swing.SwingUtilities;
038:
039: import com.vividsolutions.jts.util.Assert;
040: import com.vividsolutions.jump.feature.FeatureCollection;
041: import com.vividsolutions.jump.feature.FeatureCollectionWrapper;
042: import com.vividsolutions.jump.feature.FeatureDataset;
043: import com.vividsolutions.jump.io.datasource.DataSourceQuery;
044: import com.vividsolutions.jump.util.Blackboard;
045: import com.vividsolutions.jump.workbench.ui.plugin.AddNewLayerPlugIn;
046: import com.vividsolutions.jump.workbench.ui.renderer.style.*;
047:
048: /**
049: * Adds colour, line-width, and other stylistic information to a Feature
050: * Collection.
051: * <p>
052: * When adding or removing multiple features to this Layer's FeatureCollection,
053: * prefer #addAll and #removeAll to #add and #remove -- fewer events will be
054: * fired.
055: */
056: public class Layer extends AbstractLayerable implements
057: LayerManagerProxy {
058: public static final String FIRING_APPEARANCE_CHANGED_ON_ATTRIBUTE_CHANGE = Layer.class
059: .getName()
060: + " - FIRING APPEARANCE CHANGED ON ATTRIBUTE CHANGE";
061:
062: private String description = "";
063:
064: private boolean drawingLast = false;
065:
066: private FeatureCollectionWrapper featureCollectionWrapper;
067:
068: private ArrayList styles = new ArrayList();
069:
070: private boolean synchronizingLineColor = true;
071:
072: private boolean editable = false;
073:
074: private boolean selectable = true;
075:
076: private boolean readonly = false;
077:
078: private LayerListener layerListener = null;
079:
080: private Blackboard blackboard = new Blackboard() {
081: private static final long serialVersionUID = 6504993615735124204L;
082: {
083: put(FIRING_APPEARANCE_CHANGED_ON_ATTRIBUTE_CHANGE, true);
084: }
085: };
086:
087: private boolean featureCollectionModified = false;
088:
089: private DataSourceQuery dataSourceQuery;
090:
091: /**
092: * Called by Java2XML
093: */
094: public Layer() {
095: }
096:
097: public Layer(String name, Color fillColor,
098: FeatureCollection featureCollection,
099: LayerManager layerManager) {
100: super (name, layerManager);
101: Assert.isTrue(featureCollection != null);
102:
103: //Can't fire events because this Layerable hasn't been added to the
104: //LayerManager yet. [Jon Aquino]
105: boolean firingEvents = layerManager.isFiringEvents();
106: layerManager.setFiringEvents(false);
107:
108: try {
109: addStyle(new BasicStyle());
110: addStyle(new SquareVertexStyle());
111: addStyle(new LabelStyle());
112: } finally {
113: layerManager.setFiringEvents(firingEvents);
114: }
115:
116: getBasicStyle().setFillColor(fillColor);
117: getBasicStyle().setLineColor(defaultLineColor(fillColor));
118: getBasicStyle().setAlpha(150);
119: setFeatureCollection(featureCollection);
120: }
121:
122: /**
123: * @return a darker version of the given fill colour, for use as the line
124: * colour
125: */
126: public static Color defaultLineColor(Color fillColor) {
127: return fillColor.darker();
128: }
129:
130: public void setDescription(String description) {
131: Assert
132: .isTrue(
133: description != null,
134: "Java2XML requires that the description be non-null. Use an empty string if necessary.");
135: this .description = description;
136: }
137:
138: /**
139: * @return true if this layer should always be 'readonly' I.e.: The layer
140: * should never have the editable field set to true.
141: */
142: public boolean isReadonly() {
143: return readonly;
144: }
145:
146: /**
147: * @Set whether this layer can be made editable.
148: */
149: public void setReadonly(boolean value) {
150: readonly = value;
151: }
152:
153: /**
154: * @return true if features in this layer can be selected.
155: */
156: public boolean isSelectable() {
157: return selectable;
158: }
159:
160: /**
161: * Set whether or not features in this layer can be selected.
162: * @param value true if features in this layer can be selected
163: */
164: public void setSelectable(boolean value) {
165: selectable = value;
166: }
167:
168: /**
169: * Used for lightweight layers like the Vector layer.
170: *
171: * @param drawingLast
172: * true if the layer should be among those drawn last
173: */
174: public void setDrawingLast(boolean drawingLast) {
175: this .drawingLast = drawingLast;
176: fireAppearanceChanged();
177: }
178:
179: public void setFeatureCollection(
180: final FeatureCollection featureCollection) {
181: final FeatureCollection oldFeatureCollection = featureCollectionWrapper != null ? featureCollectionWrapper
182: .getUltimateWrappee()
183: : AddNewLayerPlugIn.createBlankFeatureCollection();
184: ObservableFeatureCollection observableFeatureCollection = new ObservableFeatureCollection(
185: featureCollection);
186: observableFeatureCollection.checkNotWrappingSameClass();
187: observableFeatureCollection
188: .add(new ObservableFeatureCollection.Listener() {
189: public void featuresAdded(Collection features) {
190: getLayerManager().fireFeaturesChanged(features,
191: FeatureEventType.ADDED, Layer.this );
192: }
193:
194: public void featuresRemoved(Collection features) {
195: getLayerManager().fireFeaturesChanged(features,
196: FeatureEventType.DELETED, Layer.this );
197: }
198: });
199:
200: if ((getLayerManager() != null)
201: && getLayerManager().getLayers().contains(this )) {
202: //Don't fire APPEARANCE_CHANGED immediately, to avoid the
203: //following problem:
204: //(1) Add fence layer
205: //(2) LAYER_ADDED event will be called
206: //(3) APPEARANCE_CHANGED will be fired in this method (before
207: //the JTree receives its LAYER_ADDED event)
208: //(4) The JTree will complain because it gets the
209: // APPEARANCE_CHANGED
210: //event before the LAYER_ADDED event:
211: // java.lang.ArrayIndexOutOfBoundsException: 0 >= 0
212: // at java.util.Vector.elementAt(Vector.java:412)
213: // at
214: // javax.swing.tree.DefaultMutableTreeNode.getChildAt(DefaultMutableTreeNode.java:226)
215: // at
216: // javax.swing.tree.VariableHeightLayoutCache.treeNodesChanged(VariableHeightLayoutCache.java:369)
217: // at
218: // javax.swing.plaf.basic.BasicTreeUI$TreeModelHandler.treeNodesChanged(BasicTreeUI.java:2339)
219: // at
220: // javax.swing.tree.DefaultTreeModel.fireTreeNodesChanged(DefaultTreeModel.java:435)
221: // at
222: // javax.swing.tree.DefaultTreeModel.nodesChanged(DefaultTreeModel.java:318)
223: // at
224: // javax.swing.tree.DefaultTreeModel.nodeChanged(DefaultTreeModel.java:251)
225: // at
226: // com.vividsolutions.jump.workbench.model.LayerTreeModel.layerChanged(LayerTreeModel.java:292)
227: //[Jon Aquino]
228: SwingUtilities.invokeLater(new Runnable() {
229: public void run() {
230: //Changed APPEARANCE_CHANGED event to FEATURE_DELETED and
231: //FEATURE_ADDED events, but I think the lengthy comment
232: //above still applies. [Jon Aquino]
233: //Drop #isEmpty checks, so that database-backed feature
234: //collections don't have to implement it.
235: //[Jon Aquino 2005-03-02]
236: getLayerManager().fireFeaturesChanged(
237: oldFeatureCollection.getFeatures(),
238: FeatureEventType.DELETED, Layer.this );
239: getLayerManager().fireFeaturesChanged(
240: featureCollection.getFeatures(),
241: FeatureEventType.ADDED, Layer.this );
242: }
243: });
244: }
245:
246: setFeatureCollectionWrapper(observableFeatureCollection);
247: }
248:
249: /**
250: * Editability is not enforced; all parties are responsible for heeding this
251: * flag.
252: */
253: public void setEditable(boolean editable) {
254: if (this .editable == editable) {
255: return;
256: }
257:
258: this .editable = editable;
259: fireLayerChanged(LayerEventType.METADATA_CHANGED);
260: }
261:
262: public boolean isEditable() {
263: return editable;
264: }
265:
266: public void setSynchronizingLineColor(boolean synchronizingLineColor) {
267: this .synchronizingLineColor = synchronizingLineColor;
268: fireAppearanceChanged();
269: }
270:
271: public BasicStyle getBasicStyle() {
272: return (BasicStyle) getStyle(BasicStyle.class);
273: }
274:
275: public VertexStyle getVertexStyle() {
276: return (VertexStyle) getStyle(VertexStyle.class);
277: }
278:
279: public LabelStyle getLabelStyle() {
280: return (LabelStyle) getStyle(LabelStyle.class);
281: }
282:
283: public String getDescription() {
284: return description;
285: }
286:
287: /**
288: * Returns a wrapper around the FeatureCollection which was added using
289: * #wrapFeatureCollection. The original FeatureCollection can be retrieved
290: * using FeatureCollectionWrapper#getWrappee. However, parties are
291: * encouraged to use the FeatureCollectionWrapper instead, so that feature
292: * additions and removals cause FeatureEvents to be fired (by the Layer).
293: */
294: public FeatureCollectionWrapper getFeatureCollectionWrapper() {
295: return featureCollectionWrapper;
296: }
297:
298: protected void setFeatureCollectionWrapper(
299: FeatureCollectionWrapper featureCollectionWrapper) {
300: this .featureCollectionWrapper = featureCollectionWrapper;
301: }
302:
303: /**
304: * Styles do not notify the Layer when their parameters change. Therefore,
305: * after you modify a Style's parameters (for example, the fill colour of
306: * BasicStyle), be sure to call #fireAppearanceChanged
307: *
308: * @param c
309: * Can even be the desired Style's superclass or interface
310: * @return The style value
311: */
312: public Style getStyle(Class c) {
313: for (Iterator i = styles.iterator(); i.hasNext();) {
314: Style p = (Style) i.next();
315:
316: if (c.isInstance(p)) {
317: return p;
318: }
319: }
320:
321: return null;
322: }
323:
324: public List getStyles() {
325: return Collections.unmodifiableList(styles);
326: }
327:
328: public boolean hasReadableDataSource() {
329: return dataSourceQuery != null
330: && dataSourceQuery.getDataSource().isReadable();
331: }
332:
333: public boolean isDrawingLast() {
334: return drawingLast;
335: }
336:
337: public boolean isSynchronizingLineColor() {
338: return synchronizingLineColor;
339: }
340:
341: public void addStyle(Style style) {
342: styles.add(style);
343: fireAppearanceChanged();
344: }
345:
346: /**
347: * Releases references to the data, to facilitate garbage collection.
348: * Important for MDI apps like the JUMP Workbench. Called when the last
349: * JInternalFrame viewing the LayerManager is closed (i.e. internal frame's
350: * responsibility). To conserve memory, if layers are frequently added and
351: * removed from the LayerManager, parties may want to call #dispose
352: * themselves rather than waiting for the internal frame to be closed.
353: */
354: public void dispose() {
355: //Don't just call FeatureCollection#removeAll, because it may be a
356: // database
357: //table, and we don't want to delete its contents! [Jon Aquino]
358: setFeatureCollection(AddNewLayerPlugIn
359: .createBlankFeatureCollection());
360: }
361:
362: public void removeStyle(Style p) {
363: Assert.isTrue(styles.remove(p));
364: fireAppearanceChanged();
365: }
366:
367: public Collection cloneStyles() {
368: ArrayList styleClones = new ArrayList();
369:
370: for (Iterator i = getStyles().iterator(); i.hasNext();) {
371: Style style = (Style) i.next();
372: styleClones.add(style.clone());
373: }
374:
375: return styleClones;
376: }
377:
378: public void setStyles(Collection newStyles) {
379: boolean firingEvents = getLayerManager().isFiringEvents();
380: getLayerManager().setFiringEvents(false);
381:
382: try {
383: //new ArrayList to prevent ConcurrentModificationException [Jon
384: // Aquino]
385: for (Iterator i = new ArrayList(getStyles()).iterator(); i
386: .hasNext();) {
387: Style style = (Style) i.next();
388: removeStyle(style);
389: }
390:
391: for (Iterator i = newStyles.iterator(); i.hasNext();) {
392: Style style = (Style) i.next();
393: addStyle(style);
394: }
395: } finally {
396: getLayerManager().setFiringEvents(firingEvents);
397: }
398:
399: fireAppearanceChanged();
400: }
401:
402: public void setLayerManager(LayerManager layerManager) {
403: if (layerManager != null) {
404: layerManager.removeLayerListener(getLayerListener());
405: }
406:
407: super .setLayerManager(layerManager);
408: layerManager.addLayerListener(getLayerListener());
409: }
410:
411: private LayerListener getLayerListener() {
412: //Need to create layerListener lazily because it will be called by the
413: //superclass constructor. [Jon Aquino]
414: if (layerListener == null) {
415: layerListener = new LayerListener() {
416: public void featuresChanged(FeatureEvent e) {
417: if (e.getLayer() == Layer.this ) {
418: setFeatureCollectionModified(true);
419:
420: //Before I wasn't firing appearance-changed on an
421: // attribute
422: //change. But now with labelling and colour theming,
423: //I have to. [Jon Aquino]
424: if (e.getType() != FeatureEventType.ATTRIBUTES_MODIFIED
425: || getBlackboard()
426: .get(
427: FIRING_APPEARANCE_CHANGED_ON_ATTRIBUTE_CHANGE,
428: true)) {
429: //Fixed bug above -- wasn't supplying a default
430: // value to
431: //Blackboard#getBoolean, resulting in a
432: // NullPointerException
433: //when the Layer was created using the
434: // parameterless
435: //constructor (because that constructor doesn't
436: // initialize
437: //FIRING_APPEARANCE_CHANGED_ON_ATTRIBUTE_CHANGE
438: //on the blackboard [Jon Aquino 10/21/2003]
439: fireAppearanceChanged();
440: }
441: }
442: }
443:
444: public void layerChanged(LayerEvent e) {
445: }
446:
447: public void categoryChanged(CategoryEvent e) {
448: }
449: };
450: }
451:
452: return layerListener;
453: }
454:
455: public Blackboard getBlackboard() {
456: return blackboard;
457: }
458:
459: /**
460: * Enables a layer to be changed undoably. Since the layer's features are
461: * saved, only use this method for layers with few features.
462: */
463: public static UndoableCommand addUndo(final String layerName,
464: final LayerManagerProxy proxy,
465: final UndoableCommand wrappeeCommand) {
466: return new UndoableCommand(wrappeeCommand.getName()) {
467: private Layer layer;
468:
469: private String categoryName;
470:
471: private Collection features;
472:
473: private boolean visible;
474:
475: private Layer currentLayer() {
476: return proxy.getLayerManager().getLayer(layerName);
477: }
478:
479: public void execute() {
480: layer = currentLayer();
481:
482: if (layer != null) {
483: features = new ArrayList(layer
484: .getFeatureCollectionWrapper()
485: .getFeatures());
486: categoryName = layer.getName();
487: visible = layer.isVisible();
488: }
489:
490: wrappeeCommand.execute();
491: }
492:
493: public void unexecute() {
494: wrappeeCommand.unexecute();
495:
496: if ((layer == null) && (currentLayer() != null)) {
497: proxy.getLayerManager().remove(currentLayer());
498: }
499:
500: if ((layer != null) && (currentLayer() == null)) {
501: proxy.getLayerManager().addLayer(categoryName,
502: layer);
503: }
504:
505: if (layer != null) {
506: layer.getFeatureCollectionWrapper().clear();
507: layer.getFeatureCollectionWrapper()
508: .addAll(features);
509: layer.setVisible(visible);
510: }
511: }
512: };
513: }
514:
515: /**
516: * Does nothing if the underlying feature collection is not a
517: * FeatureDataset.
518: */
519: public static void tryToInvalidateEnvelope(Layer layer) {
520: if (layer.getFeatureCollectionWrapper().getUltimateWrappee() instanceof FeatureDataset) {
521: ((FeatureDataset) layer.getFeatureCollectionWrapper()
522: .getUltimateWrappee()).invalidateEnvelope();
523: }
524: }
525:
526: public DataSourceQuery getDataSourceQuery() {
527: return dataSourceQuery;
528: }
529:
530: public Layer setDataSourceQuery(DataSourceQuery dataSourceQuery) {
531: this .dataSourceQuery = dataSourceQuery;
532:
533: return this ;
534: }
535:
536: public boolean isFeatureCollectionModified() {
537: return featureCollectionModified;
538: }
539:
540: public Layer setFeatureCollectionModified(
541: boolean featureCollectionModified) {
542: if (this.featureCollectionModified == featureCollectionModified) {
543: return this;
544: }
545:
546: this.featureCollectionModified = featureCollectionModified;
547: fireLayerChanged(LayerEventType.METADATA_CHANGED);
548:
549: return this;
550: }
551: }
|