001: /* uDig - User Friendly Desktop Internet GIS client
002: * http://udig.refractions.net
003: * (C) 2004, Refractions Research Inc.
004: *
005: * This library is free software; you can redistribute it and/or
006: * modify it under the terms of the GNU Lesser General Public
007: * License as published by the Free Software Foundation;
008: * version 2.1 of the License.
009: *
010: * This library is distributed in the hope that it will be useful,
011: * but WITHOUT ANY WARRANTY; without even the implied warranty of
012: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
013: * Lesser General Public License for more details.
014: */
015: package net.refractions.udig.ui;
016:
017: import java.io.IOException;
018: import java.util.Arrays;
019: import java.util.Collection;
020: import java.util.HashSet;
021: import java.util.Iterator;
022: import java.util.List;
023: import java.util.Set;
024: import java.util.concurrent.CopyOnWriteArraySet;
025:
026: import net.refractions.udig.core.IProvider;
027: import net.refractions.udig.internal.ui.Trace;
028: import net.refractions.udig.internal.ui.UiPlugin;
029: import net.refractions.udig.ui.internal.Messages;
030:
031: import org.eclipse.core.runtime.IAdaptable;
032: import org.eclipse.core.runtime.IProgressMonitor;
033: import org.eclipse.core.runtime.ISafeRunnable;
034: import org.eclipse.jface.viewers.ISelection;
035: import org.eclipse.jface.viewers.ISelectionChangedListener;
036: import org.eclipse.jface.viewers.ISelectionProvider;
037: import org.eclipse.jface.viewers.IStructuredSelection;
038: import org.eclipse.jface.viewers.SelectionChangedEvent;
039: import org.eclipse.jface.viewers.StructuredSelection;
040: import org.eclipse.swt.SWT;
041: import org.eclipse.swt.widgets.Display;
042: import org.eclipse.swt.widgets.Table;
043: import org.eclipse.swt.widgets.TableItem;
044: import org.geotools.data.DefaultQuery;
045: import org.geotools.data.FeatureSource;
046: import org.geotools.data.Query;
047: import org.geotools.feature.Feature;
048: import org.geotools.feature.FeatureCollection;
049: import org.geotools.feature.FeatureIterator;
050: import org.geotools.filter.AbstractFilter;
051: import org.geotools.filter.FidFilter;
052: import org.geotools.filter.Filter;
053: import org.geotools.filter.FilterFactory;
054: import org.geotools.filter.FilterFactoryFinder;
055: import org.geotools.filter.FilterType;
056: import org.geotools.filter.FilterVisitor;
057: import org.geotools.filter.IllegalFilterException;
058: import org.geotools.filter.LogicFilter;
059:
060: /**
061: * Manages selection for the {@link FeatureTableControl}
062: *
063: * @author Jesse
064: * @since 1.1.0
065: */
066: class FeatureTableSelectionProvider implements ISelectionProvider {
067:
068: private FeatureTableControl owner;
069: private Set<String> selectionFids = new HashSet<String>();
070: private Set<ISelectionChangedListener> selectionChangedListeners = new CopyOnWriteArraySet<ISelectionChangedListener>();
071:
072: /**
073: * if null then no set selection is running if not null then it must be cancelled.
074: */
075: volatile IProgressMonitor progressMonitor;
076: private IProvider<IProgressMonitor> progressMonitorProvider;
077:
078: public FeatureTableSelectionProvider(FeatureTableControl control,
079: IProvider<IProgressMonitor> progressMonitorProvider) {
080: this .owner = control;
081: this .progressMonitorProvider = progressMonitorProvider;
082: }
083:
084: public void addSelectionChangedListener(
085: ISelectionChangedListener listener) {
086: selectionChangedListeners.add(listener);
087: }
088:
089: public ISelection getSelection() {
090: checkWidget();
091: if (selectionFids.isEmpty())
092: return new StructuredSelection();
093: return new StructuredSelection(getFidFilter());
094:
095: }
096:
097: public void removeSelectionChangedListener(
098: ISelectionChangedListener listener) {
099: selectionChangedListeners.remove(listener);
100: }
101:
102: public void setSelection(ISelection selection) {
103: setSelection(selection, true);
104: }
105:
106: void setSelection(final ISelection newSelection,
107: final boolean reveal) {
108: checkWidget();
109: if (progressMonitor != null) {
110: progressMonitor.setCanceled(true);
111: UiPlugin.trace(Trace.FEATURE_TABLE,
112: FeatureTableSelectionProvider.class,
113: "#setSelection(): cancelled monitor", null); //$NON-NLS-1$
114:
115: }
116: try {
117: PlatformGIS.wait(500, -1, new WaitCondition() {
118:
119: public boolean isTrue() {
120: return progressMonitor == null;
121: }
122:
123: }, this );
124: } catch (InterruptedException e) {
125: UiPlugin.log("Interrupted", e); //$NON-NLS-1$
126: return;
127: }
128:
129: synchronized (this ) {
130: progressMonitor = progressMonitorProvider.get();
131: progressMonitor.setCanceled(false);
132:
133: PlatformGIS.run(new SelectionLoader(newSelection, reveal));
134: }
135: }
136:
137: protected void notifyListeners() {
138: // let listeners know the selection has changed.
139: SelectionChangedEvent event = new SelectionChangedEvent(owner,
140: getSelection());
141: for (ISelectionChangedListener listener : selectionChangedListeners) {
142: try {
143: listener.selectionChanged(event);
144: } catch (Throwable e) {
145: UiPlugin.log("", e); //$NON-NLS-1$
146: }
147: }
148: }
149:
150: /**
151: * Returns the fids. It can be used to add new fids. No notification of the change is raised.
152: *
153: * @return
154: */
155: public Collection<String> getSelectionFids() {
156: checkWidget();
157: return selectionFids;
158: }
159:
160: public FidFilter getFidFilter() {
161: checkWidget();
162: return new SelectionFilter(selectionFids);
163: }
164:
165: private void checkWidget() {
166: if (Display.getCurrent() == null)
167: SWT.error(SWT.ERROR_THREAD_INVALID_ACCESS);
168: }
169:
170: private static class SelectionFilter extends AbstractFilter
171: implements FidFilter {
172: private final static FilterFactory FACTORY = FilterFactoryFinder
173: .createFilterFactory();
174: final Collection<String> selectionFids;
175:
176: public SelectionFilter(Collection<String> fids) {
177: this .selectionFids = fids;
178: }
179:
180: public void addAllFids(Collection arg0) {
181: throw new UnsupportedOperationException();
182: }
183:
184: public void addFid(String arg0) {
185: throw new UnsupportedOperationException();
186: }
187:
188: public boolean contains(Feature feature) {
189: if (selectionFids == null)
190: return false;
191: return selectionFids.contains(feature.getID());
192: }
193:
194: public String[] getFids() {
195: if (selectionFids == null)
196: return new String[0];
197: return selectionFids.toArray(new String[0]);
198: }
199:
200: public void removeAllFids(Collection arg0) {
201: throw new UnsupportedOperationException();
202: }
203:
204: public void removeFid(String arg0) {
205: throw new UnsupportedOperationException();
206: }
207:
208: public short getFilterType() {
209: return FilterType.FID;
210: }
211:
212: public void accept(FilterVisitor arg0) {
213: arg0.visit(this );
214: }
215:
216: public Filter and(Filter arg0) {
217: return createLogicFilter(arg0, FilterType.LOGIC_AND);
218: }
219:
220: private Filter createLogicFilter(Filter arg0, short type) {
221: try {
222: LogicFilter filter = FACTORY.createLogicFilter(type);
223: filter.addFilter(this );
224: if (type != FilterType.LOGIC_NOT)
225: filter.addFilter(arg0);
226: return filter;
227: } catch (IllegalFilterException e) {
228: throw (RuntimeException) new RuntimeException()
229: .initCause(e);
230: }
231: }
232:
233: public Filter not() {
234: return createLogicFilter(null, FilterType.LOGIC_NOT);
235: }
236:
237: public Filter or(Filter arg0) {
238: return createLogicFilter(arg0, FilterType.LOGIC_OR);
239: }
240:
241: @Override
242: public String toString() {
243: if (selectionFids == null)
244: return ""; //$NON-NLS-1$
245: return selectionFids.toString();
246: }
247:
248: };
249:
250: private class SelectionLoader implements ISafeRunnable {
251:
252: private final ISelection newSelection;
253: private final boolean reveal;
254:
255: public SelectionLoader(ISelection newSelection, boolean reveal) {
256: this .newSelection = newSelection;
257: this .reveal = reveal;
258: }
259:
260: public void handleException(Throwable exception) {
261: UiPlugin.log(
262: "Error setting selection on table view", exception); //$NON-NLS-1$
263: }
264:
265: public void run() throws Exception {
266: startProgress();
267: try {
268: if (newSelection.isEmpty()) {
269: if (owner.getViewer().getControl().isDisposed()) {
270: done();
271: return;
272: }
273: owner.getViewer().getControl().getDisplay()
274: .asyncExec(new Runnable() {
275: public void run() {
276: updateMonitor(3);
277: owner.getViewer().getTable()
278: .setSelection(
279: new TableItem[0]);
280:
281: selectionFids.clear();
282:
283: updateMonitor(3);
284:
285: owner.getViewer().getTable()
286: .clearAll();
287: }
288:
289: });
290:
291: } else if (newSelection instanceof IStructuredSelection) {
292: IStructuredSelection structured = (IStructuredSelection) newSelection;
293: final Set<String> fids = new HashSet<String>();
294: obtainFidsFromSelection(structured, fids);
295:
296: // selection is equivalent to last selection so return
297: if (selectionFids.equals(fids))
298: return;
299:
300: FeatureTableContentProvider provider = (FeatureTableContentProvider) owner
301: .getViewer().getContentProvider();
302: final List<Feature> features = provider.features;
303: int i = 0;
304: for (Feature feature : features) {
305: if (fids.contains(feature.getID())) {
306: break;
307: }
308: i++;
309: }
310: updateMonitor(1);
311:
312: final int index = i;
313:
314: owner.getViewer().getControl().getDisplay()
315: .asyncExec(new Runnable() {
316: public void run() {
317: updateMonitor(1);
318:
319: owner.getViewer().getTable()
320: .setSelection(
321: new TableItem[0]);
322:
323: selectionFids = fids;
324:
325: final Table table = owner
326: .getViewer().getTable();
327: // clear non-virtual data so that it will be re-labelled. This is not
328: // too
329: // bad of an operation (I think)
330: table.clearAll();
331: if (reveal
332: && index < features.size()) {
333: // show selection if there is one.
334: table.setTopIndex(index);
335: }
336:
337: notifyListeners();
338: }
339: });
340: }
341: } catch (Abort e) {
342: // its ok just aborting the finally will clean up
343: } finally {
344: done();
345: }
346: }
347:
348: private void updateMonitor(final int ticks) {
349: if (Display.getCurrent() != null) {
350: progressMonitor.worked(ticks);
351: } else {
352: owner.getControl().getDisplay().asyncExec(
353: new Runnable() {
354: public void run() {
355: progressMonitor.worked(ticks);
356: }
357: });
358: }
359: }
360:
361: private void done() {
362: Runnable runnable = new Runnable() {
363: public void run() {
364: synchronized (FeatureTableSelectionProvider.this ) {
365: progressMonitor.done();
366: progressMonitor = null;
367: FeatureTableSelectionProvider.this .notifyAll();
368: }
369: }
370: };
371: if (Display.getCurrent() != null) {
372: runnable.run();
373: } else {
374: owner.getControl().getDisplay().asyncExec(runnable);
375: }
376: }
377:
378: private void startProgress() {
379: Runnable runnable = new Runnable() {
380: public void run() {
381: progressMonitor
382: .beginTask(
383: Messages.FeatureTableSelectionProvider_loading_new_selection,
384: 10);
385: progressMonitor.worked(1);
386: }
387: };
388: if (Display.getCurrent() != null) {
389: runnable.run();
390: } else {
391: owner.getControl().getDisplay().asyncExec(runnable);
392: }
393: }
394:
395: private void obtainFidsFromSelection(
396: IStructuredSelection structured, final Set<String> fids)
397: throws IOException, Abort {
398: int usedTicks = 0;
399: for (Iterator iter = structured.iterator(); iter.hasNext();) {
400:
401: if (progressMonitor.isCanceled())
402: throw new Abort();
403:
404: Object element = (Object) iter.next();
405:
406: if (element instanceof String) {
407: fids.add((String) element);
408: } else if (element instanceof Feature) {
409: fids.add(((Feature) element).getID());
410: } else if (element instanceof FidFilter) {
411: String[] fids2 = ((FidFilter) element).getFids();
412: fids.addAll(Arrays.asList(fids2));
413: } else if (element instanceof IAdaptable) {
414:
415: obtainFidsFromAdaptable(fids, (IAdaptable) element);
416: } else if (element instanceof Filter) {
417: UiPlugin
418: .log(
419: "Tried to set selection on table but the selection contained a non-Fid Filter that could not " + //$NON-NLS-1$
420: "adapt to a FeatureSource so the selection could not be applied. Ignoring filter", //$NON-NLS-1$
421: null);
422: } else {
423: UiPlugin
424: .log(
425: "Tried to set selection on table but the selection contained a " + element.getClass().getSimpleName() + //$NON-NLS-1$
426: " but this type cannot be converted to Fids",
427: null); //$NON-NLS-1$
428: }
429: if (usedTicks < 7)
430: updateMonitor(1);
431: }
432: if (usedTicks < 7)
433: updateMonitor(7 - usedTicks);
434: }
435:
436: /**
437: * Obtain fids from the features source if possible.
438: *
439: * @param fids the set to add fids to
440: * @param adaptable the object that adapted to the filter. hopefully can adapt to a feature
441: * source as well
442: * @param filter filter to use for obtaining fids.
443: * @throws IOException
444: * @throws Abort
445: */
446: private void obtainFidsFromAdaptable(final Set<String> fids,
447: IAdaptable adaptable) throws IOException, Abort {
448: Filter filter = null;
449: if (adaptable.getAdapter(Filter.class) != null)
450: filter = (Filter) adaptable.getAdapter(Filter.class);
451: else if (adaptable.getAdapter(Query.class) != null)
452: filter = ((Query) adaptable.getAdapter(Query.class))
453: .getFilter();
454:
455: if (filter == null)
456: return;
457:
458: FeatureSource source = null;
459: if (adaptable.getAdapter(FeatureSource.class) != null) {
460: source = (FeatureSource) adaptable
461: .getAdapter(FeatureSource.class);
462: }
463:
464: if (source == null) {
465: UiPlugin
466: .log(
467: "last resource run filter on features in table view... Might now work since table view" + //$NON-NLS-1$
468: " does not have any Geometries",
469: new Exception()); //$NON-NLS-1$
470:
471: if (owner.getViewer() != null
472: && owner.getViewer().getInput() != null)
473: for (Feature feature : ((FeatureTableContentProvider) owner
474: .getViewer().getContentProvider()).features) {
475: if (progressMonitor.isCanceled())
476: throw new Abort();
477: if (filter.contains(feature))
478: fids.add(feature.getID());
479: }
480: } else {
481: DefaultQuery defaultQuery = new DefaultQuery(source
482: .getSchema().getTypeName(), filter,
483: new String[0]);
484: // get features that are just fids no attributes
485: FeatureCollection features = source
486: .getFeatures(defaultQuery);
487:
488: long start = System.currentTimeMillis();
489:
490: FeatureIterator featureIterator = features.features();
491: try {
492: while (featureIterator.hasNext()) {
493: if (progressMonitor.isCanceled())
494: throw new Abort();
495: if (System.currentTimeMillis() - start > 500) {
496: start = System.currentTimeMillis();
497: owner.getViewer().getControl().getDisplay()
498: .asyncExec(new Runnable() {
499: public void run() {
500: progressMonitor
501: .subTask(fids
502: .size()
503: + " selected"); //$NON-NLS-1$
504: }
505: });
506: }
507: fids.add(featureIterator.next().getID());
508: }
509: } finally {
510: featureIterator.close();
511: }
512: }
513: }
514: }
515:
516: private static class Abort extends Exception {
517:
518: /** long serialVersionUID field */
519: private static final long serialVersionUID = 1L;
520: }
521:
522: }
|