/* uDig-Spatial Operations plugins * http://b5m.gipuzkoa.net * (C) 2006, DiputaciĆ³n Foral de Gipuzkoa, OrdenaciĆ³n Territorial. * * 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; * version 2.1 of the License. * * 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. */ package es.axios.udig.spatialoperations.ui.parameters; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Observable; import java.util.Set; import net.refractions.udig.project.ILayer; import net.refractions.udig.project.ILayerListener; import net.refractions.udig.project.IMap; import net.refractions.udig.project.IMapCompositionListener; import net.refractions.udig.project.LayerEvent; import net.refractions.udig.project.MapCompositionEvent; import net.refractions.udig.project.ui.tool.IToolContext; import org.eclipse.swt.custom.CCombo; import org.eclipse.swt.custom.CLabel; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; import org.opengis.filter.Filter; import org.opengis.referencing.crs.CoordinateReferenceSystem; import es.axios.udig.spatialoperations.internal.i18n.Messages; import es.axios.udig.spatialoperations.ui.parameters.ISOCommand.CmdStatus; import es.axios.udig.spatialoperations.ui.parameters.SpatialOperationCommand.ParameterName; import es.axios.udig.spatialoperations.ui.view.ISOPresenter; import es.axios.udig.ui.commons.mediator.AppGISMediator; import es.axios.udig.ui.commons.mediator.PlatformGISMediator; import es.axios.udig.ui.commons.message.InfoMessage; import es.axios.udig.ui.commons.util.MapUtil; /** * Composite containing the spatial operation' parameters selected * <p> * The subclass of this are responsible of presenting the content for operation * parameters. It must implement the <code>createContents()</code> method to * create the specific controls for the parameter content presenter. * * <p> * Subclasses may override these methods: * <ul> * <li><code>createContents()</code> - must be implemented to display the * specific controls</li> * <li><code>populate()</code> - may be extended or reimplemented</li> * <li><code>clearInputs()</code> - may be extended or reimplemented</li> * <li><code>changeContent()</code> - must be implemented to Check the * consistency between this presenter and its command.</li> * </ul> * </p> * </p> * <p> * To create an instance have into account the following idiom: * </p> * * <pre><code> * public MyParamsPresenter(){ * * super(parent, style); * * super.initialize(); * } * * initState() { * //set the resultComposite as observer (if that is necessary ) * ISOCommand cmd = getCommand(); * cmd.addObserver(this.aComposite1); * cmd.addObserver(this.aComposite2); * ... * } * Override * protected void createContents() { * // create the parameter widgets required by this implementation * } * * * Override * protected void populate() { * // fill widgets with default values * } * * </code> </pre> * * @see createContents() * @see populate() * @see clearInputs() * @see changeContent() * * * @author Mauricio Pazos (www.axios.es) * @author Aritz Davila (www.axios.es) * @since 1.1.0 */ public abstract class AbstractParamsPresenter extends Composite implements ISOParamsPresenter { private ISOPresenter soPresenter = null; private boolean wasInitialized = false; private ISOParamsPresenter parentParamPresenter = null; // data private IToolContext context = null; // listeners /** Map Listener used to catch the map changes */ private IMapCompositionListener mapListener = null; /** Layer Listener used to catch the layers changes */ private ILayerListener layerListener = null; /** * LF is LayerFilter list. Stores each layer and its filter, until the user * clean all the filters. TODO not implemented yet */ protected List<LF> LFList = new LinkedList<LF>(); protected Thread uiThread; private ISOCommand command = null; /** * This method must be override by subclass. A typical implementation could * have the following idiom: * * <pre><code> * public ANewParamsPresenter() { * * super(parent, style); * * super.initialize(); * * } * </code> </pre> * * @see initialize() * @param parent * @param style */ public AbstractParamsPresenter(Composite parent, int style) { super(parent, style); } public ISOCommand getCommand() { if (this.command == null) { Runnable runnable = new Runnable() { public void run() { // returns the command instance maintained in the top // presenter Composite composite = getParent(); while (!(composite instanceof ISOTopParamsPresenter)) { composite = composite.getParent(); } command = ((ISOTopParamsPresenter) composite).getCommand(); } }; Display.findDisplay(uiThread).syncExec(runnable); } return this.command; } /** * Creates the widgets for this presenter. * <p> * This is a template method which creates the component of the presenter. * The method call {@link #createContents()} that requires an * implementation. * </p> */ protected void initialize() { this.wasInitialized = false; this.uiThread = Thread.currentThread(); createContents(); initListeners(); this.wasInitialized = true; } /** * create the default listeners for spatial operations. * */ private void initListeners() { this.mapListener = new IMapCompositionListener() { public void changed(MapCompositionEvent event) { if (!wasInitialized()) { return; } // if (getCommand().isStopped()) { // return; // } updatedMapLayersActions(event); } }; this.layerListener = new ILayerListener() { public void refresh(LayerEvent event) { if (!wasInitialized()) { return; } // if (getCommand().isStopped()) { // return; // } updateLayerActions(event); } }; } /** * @return true if the presenter is ready to work, false in other case */ public boolean wasInitialized() { return (!this.isDisposed()) && (this.getCommand() != null) && (this.wasInitialized); } /** * Opens the presenter populating its widgets. * * @see clear() * @see initState(); * @see populate(); */ public void open() { Display.findDisplay(uiThread).syncExec(new Runnable() { public void run() { IMap map = getCurrentMap(); setEnabled(map != null); initState(); clearInputs(); setDefaultParameters(); } }); } /** * initializes the presenter state. Override this method if you want to do a * specific initialization of your presenter. (nothing by default) */ protected void initState() { // nothing by default } /** * Closes the spatial operation presenter. This message is sent when the * framework need to present other spatial operation selected by the user */ public void close() { getCommand().setStatus(CmdStatus.STOPPED); setEnabled(false); } /** * Creates the widgets for this presenter. This method must be called from * subclass to assure the correct initialization. */ protected abstract void createContents(); /** * Sets as Ready its command an populate the widgets with the default * values. */ private void setDefaultParameters() { ISOCommand cmd = getCommand(); cmd.setStatus(CmdStatus.STOPPED); populate(); cmd.setStatus(CmdStatus.READY); validateParameters(); }; /** * Sets the parameters on command an validate */ public boolean validateParameters() { ISOCommand cmd = getCommand(); setParametersOnCommand(cmd); if (!cmd.isReady()) { return false; } return validateData(); } /** * Should be override in subclass if the presenter has values to set in the * command */ protected void setParametersOnCommand(ISOCommand command) { // null implementation by default } /** * Sets the default values of widgets (nothing by default) */ protected abstract void populate(); /** * Display operation's parameters */ public void visible(final boolean present) { displayPresenter(present); } /** * Display operation's parameters */ protected void displayPresenter(final boolean present) { Display.findDisplay(uiThread).syncExec(new Runnable() { public void run() { setVisible(present); refresh(); } }); } /** * Clears the values of widgets */ public void clear() { Display.findDisplay(uiThread).syncExec(new Runnable() { public void run() { IMap map = getCurrentMap(); setEnabled(map != null); setDefaultValues(); clearInputs(); setDefaultParameters(); } }); } /** * Set the default parameter's values */ protected void setDefaultValues() { // nothing } /** * clears the input values of this content */ protected void clearInputs() { // nothing } /** * @return true if this presenter is the top */ public boolean isTop() { return parentParamPresenter == null; } public void setParentPresenter(final ISOParamsPresenter presenter) { assert presenter != null; this.parentParamPresenter = presenter; } /** * @return the top presenter for the operation's parameters */ public ISOParamsPresenter getTopPresenter() { ISOParamsPresenter top; if (isTop()) { top = this; } else { top = this.parentParamPresenter.getTopPresenter(); } return top; } /** * Updates the contents of this presenter */ private void refresh() { // setDefaultParameters(); } /** * Sets the context. * * @param context */ public void setContext(final IToolContext context) { IMap map; if (context == null) { // initialize or reinitialize the Presenter map = getCurrentMap(); if (map != null) { removeListenerFrom(map); } } else { // sets maps and its layers as current map = context.getMap(); if (map != null) { // add this presenter as listener of the map List<ILayer> layerList = getLayerListOf(map); addListenersTo(map, layerList); } } this.context = context; // notifies the change in current map changedMapActions(map); if (map != null) { changedLayerListActions(); validateParameters(); } } /** * @return the context */ protected IToolContext getContext() { return this.context; } /** * @return the current Map; null if there is not any Map. */ public IMap getCurrentMap() { if (this.context == null) { return null; } return this.context.getMap(); } /** * @return the current map's CRS or null if current map is null */ protected CoordinateReferenceSystem getCurrentMapCrs() { IMap map = getCurrentMap(); CoordinateReferenceSystem crs = (map != null) ? MapUtil.getCRS(map) : null; return crs; } /** * gets the layer list from a map * * @param map * @return the Layer list of map */ protected List<ILayer> getLayerListOf(IMap map) { assert map != null; return AppGISMediator.getMapLayers(map); } /** * @return the layer list of current map */ protected List<ILayer> getCurrentLayerList() { if (getCurrentMap() == null) { return Collections.emptyList(); } return AppGISMediator.getMapLayers(this.getCurrentMap()); } /** * This call back is called when the map is changed. Nothing by default you * must be override if your presenter requires specific actions. */ protected void changedMapActions(final IMap map) { // nothing by default } /** * Sets the map as current and add the listeners to listen the changes in * the map and its layers. Additionally it initializes the current layer * list. * * @param map */ private void addListenersTo(final IMap map, final List<ILayer> layerList) { assert map != null; assert layerList != null; assert this.mapListener != null; assert this.layerListener != null; map.addMapCompositionListener(this.mapListener); for (ILayer layer : layerList) { layer.addListener(this.layerListener); } } /** * Removes the listeners from map. * * @param currentMap */ private void removeListenerFrom(IMap map) { assert map != null; assert this.mapListener != null; assert this.layerListener != null; for (ILayer layer : getCurrentLayerList()) { layer.removeListener(this.layerListener); } map.removeMapCompositionListener(this.mapListener); } /** * This method is called if the collection of layer is updated (added or * removed). This is a template method that calls a specific method by each * event type. * * @see changedLayerListActions() * @see addedLayerActions() * @see removedLayerActions() * @param event */ private void updatedMapLayersActions(final MapCompositionEvent event) { MapCompositionEvent.EventType eventType = event.getType(); switch (eventType) { case ADDED: { Display.findDisplay(uiThread).asyncExec(new Runnable() { public void run() { final ILayer layer = event.getLayer(); addedLayerActions(layer); validateParameters(); } }); break; } case REMOVED: { Display.findDisplay(uiThread).asyncExec(new Runnable() { public void run() { final ILayer layer = event.getLayer(); removedLayerActions(layer); validateParameters(); } }); break; } case MANY_ADDED: case MANY_REMOVED: Display.findDisplay(uiThread).asyncExec(new Runnable() { public void run() { changedLayerListActions(); validateParameters(); } }); break; default: break; } } /** * This is a callback method which should be used to updates the content of * its widget, checking the list layer in map. */ protected void changedLayerListActions() { // nothing } /** * This is a callback method, It should be used to implement the actions * required when a layer is deleted from map. * <p> * This method provide a default implementation which remove the listener. * You can override this method to provide specific actions, Do not forget * call this method to maintain the listener list. * </p> * * @param layer */ protected void removedLayerActions(final ILayer layer) { layer.removeListener(this.layerListener); // TODO implement removeLayerListActions(layer). removeLayerListActions(layer); changedLayerListActions(); } /** * Should be redefined if the subclass requires an specific behaviour. */ protected void removeLayerListActions(ILayer layer) { // null implementation. } /** * This is a callback method, It should be used to implement the actions * required when a new layer is added to map. The event occurs when a layer * is created or added to the map. * <p> * This method provide a default implementation which add a * {@link ILayerListener} Do not forget call this method to maintain the * listener list. * </p> * * @param layer */ protected void addedLayerActions(final ILayer layer) { layer.addListener(this.layerListener); changedLayerListActions(); } /** * This method is called when a layer is changed. * * @param event * * @see {@link #changedFilterSelectionActions(ILayer, Filter)} * @see {@link #changedLayerActions(ILayer)} */ private void updateLayerActions(final LayerEvent event) { final ILayer modifiedLayer = event.getSource(); PlatformGISMediator.syncInDisplayThread(new Runnable() { public void run() { LayerEvent.EventType type = event.getType(); switch (type) { case ALL: changedLayerActions(modifiedLayer); break; case FILTER: Filter newFilter = modifiedLayer.getFilter(); changedFilterSelectionActions(modifiedLayer, newFilter); break; default: break; } } }); } /** * This is a callback method, It should be used to implement the actions * required when the features selected in layer are changed. The event * occurs when a layer is created or added to the map. * * @param layer * @param newFilter * the filter or Filter.ALL if there is no any feature selected */ protected void changedFilterSelectionActions(final ILayer layer, final Filter newFilter) { // nothing as default implementation } /** * This is a callback method, It should be used to implement the actions * required when a layer is modified. The event occurs when a layer is * created or added to the map. * * @param modifiedLayer * the modified layer */ protected void changedLayerActions(final ILayer modifiedLayer) { // nothing by default implementation } /** * Gets the selected layer from combo box * * @param comboLayer * @param textFeatures * @return the layer selected */ protected ILayer getSelecedLayer(final CCombo comboLayer) { ILayer currentLayer; int index = comboLayer.getSelectionIndex(); if (index == -1) { currentLayer = null; } else { final String layerName = comboLayer.getItem(index); currentLayer = (ILayer) comboLayer.getData(layerName); } return currentLayer; } /** * Look at the layer filter, if the filter is EXCLUDE or INCLUDE, return All * that means all the features will be processed, if the filter is different * from those, return BBox, that means only the selected features will be * processed. * * @param layer * @param filter * @param textSelectedFeatures * @return FeatureCollection the selected features. If any feature is * selected returns all layer's features. */ protected void presentSelectionAllOrBBox(final ILayer layer, final Filter filter, final CLabel textSelectedFeatures) { assert layer != null : "illegal argument, layer cannot be null"; //$NON-NLS-1$ assert filter != null : "illegal argument, filter cannot be null"; //$NON-NLS-1$ String strCount = ""; //$NON-NLS-1$ LF layerFilter = getLayerFromLF(layer); if (layerFilter != null) { // FIXME uncomment that when LF(layerFilter) works, for now on, next // IF is a patch. if (Filter.EXCLUDE.equals(filter) || Filter.INCLUDE.equals(filter)) { // if (layerFilter.getFilter().equals(Filter.ALL) // || layerFilter.getFilter().equals(Filter.NONE)) { strCount = Messages.AbstractParamsPresenter_all_features_selected; } else { strCount = "BBox."; //$NON-NLS-1$ } } else { if (Filter.EXCLUDE.equals(filter) || Filter.INCLUDE.equals(filter)) { strCount = Messages.AbstractParamsPresenter_all_features_selected; } else { strCount = "BBox."; //$NON-NLS-1$ } } textSelectedFeatures.setText(strCount); textSelectedFeatures.setToolTipText(filter.toString()); } /** * Adjusts the layer's filter * <p> * Note: filter ALL has not sense in spatial operation context * </p> * * @param currentSourceLayer * @return Filter */ protected Filter getFilter(ILayer layer) { assert layer != null; Filter filter = layer.getFilter(); if (Filter.EXCLUDE.equals(filter)) { filter = Filter.INCLUDE; } assert (filter != null) && (!Filter.EXCLUDE.equals(filter)) : "illegal postcondition: filter cannot be null"; //$NON-NLS-1$ return filter; } /** * Updates the widgets that contain the layers. The current selected layer * is maintained only if the current layer have not been deleted. */ protected void changeSelectedLayer(final ILayer currentLayer, final CCombo comboToChange) { if (currentLayer == null) { return; } // maintains the selection if the current layer have not been deleted, // otherwise delete the selection. String currentLayerName; int index; currentLayerName = currentLayer.getName(); index = comboToChange.indexOf(currentLayerName); if (index != -1) { comboToChange.select(index); } else { comboToChange.clearSelection(); } } /** * Load an specific comboBox and filter the compatible layers. * * @param comboLayer * @param domain * Get domain for a certain parameter. */ protected void loadComboWithLayerList(CCombo comboLayer, ParameterName domain) { comboLayer.removeAll(); List<ILayer> list = getCurrentLayerList(); // get the compatible geometries ISOCommand cmd = getCommand(); List<?> geometries = cmd.getDomainValues(domain); for (ILayer layer : list) { Class<?> type = layer.getSchema().getGeometryDescriptor().getType().getBinding(); if (geometries.contains(type)) { String name = layer.getName(); comboLayer.add(name); comboLayer.setData(name, layer); } } } /** * load the list of layers in the specified combobox * * @param comboLayer combobox to be loaded */ protected void loadComboWithLayerList(CCombo comboLayer) { comboLayer.removeAll(); for (ILayer layer : getCurrentLayerList()) { String name = layer.getName(); comboLayer.add(name); comboLayer.setData(name, layer); } } /** * Return the number of layer contained on the map. * * @return */ protected int getLayerCount() { List<ILayer> layerList = getCurrentLayerList(); return layerList.size(); } /** * Update the LayerFilter list. If it is empty, add the layers, and if the * user have added a new layer, also add it to the list. */ private void updateLFList() { if (this.LFList.size() == 0) { addLF(); return; } removeLF(); List<ILayer> layerList = getCurrentLayerList(); Iterator<ILayer> layerIterator = layerList.iterator(); ILayer layer = null; while (layerIterator.hasNext()) { layer = layerIterator.next(); if (!layerFilterContains(layer)) { this.LFList.add(new LF(layer)); } } } /** * Look at the <code>layer</code> in the <code>LayerFilterList</code>. * If the list contains the <code>layer</code> , return true. * * @param layer * The layer * @return If it find, return true. */ private boolean layerFilterContains(ILayer layer) { assert layer != null; Iterator<LF> iterator = this.LFList.iterator(); LF layerFilter = null; while (iterator.hasNext()) { layerFilter = iterator.next(); if (layer.equals(layerFilter.getLayer())) { return true; } } return false; } /** * Update the LayerFilter list and stores the filter of each layer until all * the filters become Filter.EXCLUDE, that means, the users clear are the * filters. */ protected void setLayerAndFilter() { updateLFList(); storeFilters(); } /** * If the filters are distinct of <code>Filter.EXCLUDE</code>, stores it. * When the user select features from layer 1 on a bbox, we store the filter * because when the user try to select whit a bbox another features from * layer 2, the layer 1 filter become Filter.EXCLUDE and we lost the * features selected from the layer 1. * * This function will store the filters, so we wont loose that filters. */ private void storeFilters() { List<ILayer> layerList = getCurrentLayerList(); Iterator<ILayer> layerIterator = layerList.iterator(); ILayer currentLayer = null; LF LFlayer = null; Filter filter = null; while (layerIterator.hasNext()) { currentLayer = layerIterator.next(); filter = currentLayer.getFilter(); if (!Filter.EXCLUDE.equals(filter) && !Filter.INCLUDE.equals(filter)) { LFlayer = getLayerFromLF(currentLayer); if (LFlayer != null) { LFlayer.setFilter(filter); this.LFList.set(this.LFList.indexOf(LFlayer), LFlayer); } } } } /** * Look at the <code>layer</code> on the <code>LFList</code> and if it * contains, return the current <code>LFList</code> registry. If not, return * null. * * @param layer * The layer to retrieve * @return The LayerFilter registry that contains the correspondent * <code>layer</code> */ private LF getLayerFromLF(ILayer layer) { assert layer != null; Iterator<LF> iterator = this.LFList.iterator(); LF layerFilter = null; while (iterator.hasNext()) { layerFilter = iterator.next(); if (layer.equals(layerFilter.getLayer())) { return layerFilter; } } // Doesn't contains. return null; } /** * Set all the filter of the <code>LFList</code> to * <code>Filter.EXCLUDE</code>. */ protected void resetLF() { Iterator<LF> iterator = this.LFList.iterator(); LF layerFilter = null; while (iterator.hasNext()) { layerFilter = iterator.next(); layerFilter.setFilter(Filter.EXCLUDE); } } /** * Add current layers and filters to the list. */ private void addLF() { List<ILayer> layerList = getCurrentLayerList(); Iterator<ILayer> layerIterator = layerList.iterator(); ILayer layer = null; while (layerIterator.hasNext()) { layer = layerIterator.next(); this.LFList.add(new LF(layer)); } } /** * When a layer is removed from the map, we assure that the {@link #LFList} * contains only the actual layers. */ protected void removeLF() { List<ILayer> layerList = getCurrentLayerList(); Set<LF> layerFilterToRemove = new HashSet<LF>(); Iterator<LF> iterator = this.LFList.iterator(); LF layerFilter = null; while (iterator.hasNext()) { layerFilter = iterator.next(); if (!layerList.contains(layerFilter.getLayer())) { layerFilterToRemove.add(layerFilter); } } this.LFList.removeAll(layerFilterToRemove); } /** * Check if the layers have some feature selected, its filter is distinct of * <code>Filter.EXCLUDE</code> * * @return True if all the layers have <code>Filter.EXCLUDE</code> */ protected boolean layersHaveNotSelection() { List<ILayer> layerList = getCurrentLayerList(); Iterator<ILayer> layerIterator = layerList.iterator(); ILayer layer = null; Filter filter = null; while (layerIterator.hasNext()) { layer = layerIterator.next(); filter = layer.getFilter(); if (!Filter.EXCLUDE.equals(filter) && !Filter.INCLUDE.equals(filter)) { return false; } } return true; } /** * LayerAndFilter class. * * A class that will store a layer and its filter. * */ protected class LF { private ILayer layer = null; private Filter filter = null; /** * LayerAndFilter constructor. Stores the Layer and its filter. */ public LF(ILayer layer) { this.layer = layer; this.filter = layer.getFilter(); } public ILayer getLayer() { return layer; } public void setLayer(ILayer layer) { this.layer = layer; } public Filter getFilter() { return filter; } public void setFilter(Filter filter) { this.filter = filter; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((filter == null) ? 0 : filter.hashCode()); result = prime * result + ((layer == null) ? 0 : layer.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (!(obj instanceof LF)) { return false; } final LF other = (LF) obj; if (filter == null) { if (other.filter != null) { return false; } } else if (!filter.equals(other.filter)) { return false; } if (layer == null) { if (other.layer != null) { return false; } } else if (!layer.equals(other.layer)) { return false; } return true; } } public InfoMessage getDefaultMessage() { return getCommand().getDefaultMessage(); } public InfoMessage getMessage() { return getCommand().getMessage(); } public String getOperationID() { return this.getCommand().getOperationID(); } public final ISOPresenter getOperationPresenter() { return this.soPresenter; } public final void setMessage(final InfoMessage message) { this.soPresenter.displayMessage(message); } public void setSpatialOperationPresenter(ISOPresenter soPresenter) { assert soPresenter != null; this.soPresenter = soPresenter; } public void executeCommand() { try { getCommand().execute(); reset(); } catch (SOCommandException e) { final InfoMessage msg = new InfoMessage(e.getMessage(), InfoMessage.Type.FAIL); setMessage(msg); return; } } public void reset() { getCommand().reset(); this.soPresenter.initializeInputs(); } private boolean validateData() { ISOCommand cmd = getCommand(); boolean result = cmd.evalPrecondition(); ISOPresenter soPresenter = getOperationPresenter(); soPresenter.displayMessage(cmd.getMessage()); return result; } /** * Redefine this method to retrieve: * <ul> * <li>the domain values for the parameters from command</li> * <li>parameters values present in the command</li> * </ul> */ public void update(Observable o, Object arg) { // will implement the get domain. } }