/*
* uDig - User Friendly Desktop Internet GIS client
* http://udig.refractions.net
* (C) 2004, Refractions Research Inc.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* (http://www.eclipse.org/legal/epl-v10.html), and the Refractions BSD
* License v1.0 (http://udig.refractions.net/files/bsd3-v10.html).
*
*/
package org.locationtech.udig.project.ui.internal.actions;
import java.io.File;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
import org.locationtech.udig.core.Pair;
import org.locationtech.udig.core.filter.AdaptingFilter;
import org.locationtech.udig.project.ILayer;
import org.locationtech.udig.project.IMap;
import org.locationtech.udig.project.IProjectElement;
import org.locationtech.udig.project.command.MapCommand;
import org.locationtech.udig.project.command.UndoableMapCommand;
import org.locationtech.udig.project.command.factory.EditCommandFactory;
import org.locationtech.udig.project.internal.Layer;
import org.locationtech.udig.project.internal.Project;
import org.locationtech.udig.project.internal.ProjectElement;
import org.locationtech.udig.project.internal.ProjectPlugin;
import org.locationtech.udig.project.internal.commands.edit.DeleteManyFeaturesCommand;
import org.locationtech.udig.project.preferences.PreferenceConstants;
import org.locationtech.udig.project.ui.ApplicationGIS;
import org.locationtech.udig.project.ui.UDIGGenericAction;
import org.locationtech.udig.project.ui.commands.DrawCommandFactory;
import org.locationtech.udig.project.ui.commands.IDrawCommand;
import org.locationtech.udig.project.ui.internal.ApplicationGISInternal;
import org.locationtech.udig.project.ui.internal.Messages;
import org.locationtech.udig.project.ui.internal.ProjectUIPlugin;
import org.locationtech.udig.project.ui.internal.UDIGEditorInputDescriptor;
import org.locationtech.udig.project.ui.render.displayAdapter.ViewportPane;
import org.locationtech.udig.ui.PlatformGIS;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.Platform;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.dialogs.MessageDialogWithToggle;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.PlatformUI;
import org.opengis.feature.simple.SimpleFeature;
/**
* Deletes the selected elements from a project.
*
* @author jeichar
* @since 0.3
*/
public class Delete extends UDIGGenericAction {
/**
* Indicates whether to run the commands synchronously or not.
*/
private boolean runSync = false;
private boolean canDeleteProjectElements;
public Delete( boolean canDeleteProjectElements){
this.canDeleteProjectElements = canDeleteProjectElements;
}
/**
* Run has been overriden to ignore deleting maps.
* <p>
* This is due to UDIG-1836 where it is noted that the default selection
* provided by a MapEditor is a MapImpl (and thus 90% of the time hitting delete
* will result in the map being removed).
* <p>
* The ProjectExplorer view and LayerView make use of their own delete action and thus are
* not effected here.
*
* @see org.eclipse.ui.IActionDelegate#run(org.eclipse.jface.action.IAction)
*/
public void run( IAction action ) {
ISelection sel = getSelection();
if (sel == null || sel.isEmpty() || !(sel instanceof IStructuredSelection)) {
return; // nothing selected to delete
}
IStructuredSelection selection = (IStructuredSelection) sel;
/*
* Optimization for a set of objects in selection of the same nature. The goal: run an
* operation once over all selected objects.
*/
ArrayList<Layer> layers = new ArrayList<Layer>(selection.size());
Object firstElem = selection.iterator().next();
Pair<Boolean, Integer> stateData;
if (canDeleteProjectElements && firstElem instanceof Project) {
stateData = showErrorMessage(selection.size(), (Project) firstElem);
} else if (canDeleteProjectElements && firstElem instanceof IProjectElement) {
stateData = showErrorMessage(selection.size(), (ProjectElement) firstElem);
} else if (firstElem instanceof Layer) {
stateData = showErrorMessage(selection.size(), (Layer) firstElem);
} else if (firstElem instanceof SimpleFeature) {
stateData = showErrorMessage(selection.size(), (SimpleFeature) firstElem);
} else if (firstElem instanceof AdaptingFilter) {
AdaptingFilter<?> f = (AdaptingFilter<?>) firstElem;
ILayer layer = (ILayer) f.getAdapter(ILayer.class);
stateData = showErrorMessage(selection.size(), layer,f);
} else {
stateData = null;
}
if( stateData == null || stateData.getRight() == Window.CANCEL ){
return; // thanks for playing
}
for( Iterator<?> iter = selection.iterator(); iter.hasNext(); ) {
Object element = iter.next();
if (canDeleteProjectElements && element instanceof Project) {
operate((Project) element, stateData);
} else if (canDeleteProjectElements && element instanceof IProjectElement) {
operate((ProjectElement) element, stateData);
} else if (element instanceof Layer) {
layers.add((Layer) element);
} else if (element instanceof SimpleFeature) {
operate((SimpleFeature) element, stateData);
}else if (element instanceof AdaptingFilter) {
AdaptingFilter<?> f = (AdaptingFilter<?>) element;
ILayer layer = (ILayer) f.getAdapter(ILayer.class);
operate(layer,f, stateData);
}
}
if (!layers.isEmpty()) {
operate(layers.toArray(new Layer[layers.size()]), stateData);
}
// layers = null;
}
/**
* @see org.locationtech.udig.project.ui.UDIGGenericAction#operate(org.locationtech.udig.project.Layer)
*/
protected void operate( Layer layer ) {
if (layer == null || layer.getMap() == null)
return;
UndoableMapCommand command = EditCommandFactory.getInstance().createDeleteLayers(
new ILayer[]{layer});
// UndoableMapCommand command =
// EditCommandFactory.getInstance().createDeleteLayer((ILayer)layer);
executeCommand(command, layer.getMapInternal());
}
/**
* @see org.locationtech.udig.project.ui.UDIGGenericAction#operate(org.locationtech.udig.project.internal.Layer[])
*/
@Override
protected void operate( Layer[] layers, Object context ) {
Pair<Boolean, Integer> pair = (Pair<Boolean, Integer>) context;
if( pair == null || pair.right() == Window.CANCEL){
return; // nothing to do
}
if (layers != null && layers.length > 0) {
/*
* Layers can exist in different maps. For each map the standalone command should be
* used to remove only layers that are contained in this map.
*/
HashMap<IMap, List<Layer>> distributor = new HashMap<IMap, List<Layer>>();
for( Layer layer : layers ) {
IMap map = layer.getMap();
if (distributor.containsKey(map)) {
distributor.get(map).add(layer);
} else {
List<Layer> list = new ArrayList<Layer>();
list.add(layer);
distributor.put(map, list);
}
}
for( Entry<IMap, List<Layer>> entry : distributor.entrySet() ) {
IMap map = entry.getKey();
Layer[] removedLayers = entry.getValue().toArray(new Layer[0]);
UndoableMapCommand command = EditCommandFactory.getInstance().createDeleteLayers(
removedLayers);
executeCommand(command, map);
}
}
}
void executeCommand( MapCommand command, IMap map ) {
if (runSync) {
map.sendCommandSync(command);
} else {
map.sendCommandASync(command);
}
}
/**
* @see org.locationtech.udig.project.ui.UDIGGenericAction#operate(org.locationtech.udig.project.IProjectElement)
*/
@SuppressWarnings("unchecked")
protected void operate( ProjectElement element, Object context ) {
if (element == null)
return;
Pair<Boolean, Integer> pair = (Pair<Boolean, Integer>) context;
if( pair == null || pair.right() == Window.CANCEL){
return; // nothing to do
}
boolean deleteFiles = pair.left();
int returnCode = pair.right();
doDelete(element, deleteFiles, returnCode);
}
@Override
protected Pair<Boolean, Integer> showErrorMessage( int size, ILayer layer, AdaptingFilter firstElement ) {
return new Pair<Boolean, Integer>(false,Window.OK);
}
@Override
protected Pair<Boolean, Integer> showErrorMessage( int size, Layer firstElement ) {
return new Pair<Boolean, Integer>(false,Window.OK);
}
@Override
protected Pair<Boolean, Integer> showErrorMessage( int size, SimpleFeature firstElement ) {
return new Pair<Boolean, Integer>(false,Window.OK);
}
@Override
protected Pair<Boolean, Integer> showErrorMessage( int size, ProjectElement element ) {
String deleteOne = Messages.Delete_deleteElement;
String name = element.getName();
String deleteMany = Messages.Delete_deleteMultipleElements;
return dialog(size, deleteOne, name, deleteMany);
}
@Override
protected Pair<Boolean, Integer> showErrorMessage( int size, Project element ) {
String deleteOne = Messages.Delete_deleteProject;
String name = element.getName();
String deleteMany = Messages.Delete_deleteMultipleProjects;
return dialog(size, deleteOne, name, deleteMany);
}
private Pair<Boolean, Integer> dialog( int size, String deleteOne, String name, String deleteMany ) {
String message;
if (size == 1) {
message = MessageFormat.format(deleteOne, name);
} else {
message = MessageFormat.format(deleteMany, size);
}
/*
* FIXME as of https://jira.codehaus.org/browse/UDIG-1974
* there is a bug in the maps removal.
* The below should be fixed once the bug has been fixed.
*/
// START OLD CODE
// MessageDialogWithToggle dialog = MessageDialogWithToggle.openOkCancelConfirm(Display
// .getCurrent().getActiveShell(), Messages.Delete_delete, message,
// Messages.Delete_filesystem, getDoDelete(), null, null);
// note: we will do our own preference store persistence, since the built in one is
// backwards
// boolean deleteFiles = dialog.getToggleState();
// int returnCode = dialog.getReturnCode();
// if (returnCode == Window.OK) {
// END OLD CODE
// START TEMPORARY NEW CODE
boolean delete = MessageDialog.openConfirm( Display.getCurrent().getActiveShell(), Messages.Delete_delete, message );
boolean deleteFiles = false;
// END TEMPORARY NEW CODE
if( delete ){
if (deleteFiles != getDoDelete()) {
setDoDelete(deleteFiles);
}
return Pair.create(deleteFiles, Window.OK);
} else {
// Window.CANCEL
return Pair.create(null, Window.CANCEL);
}
}
@Override
protected void operate( ILayer layer, AdaptingFilter filter, Object context ) {
Pair<Boolean, Integer> pair = (Pair<Boolean, Integer>) context;
if( pair == null || pair.right() == Window.CANCEL){
return; // nothing to do
}
layer.getMap().sendCommandASync(new DeleteManyFeaturesCommand(layer, filter));
}
protected final void doDelete( ProjectElement element, boolean deleteFiles, int returncode ) {
if (returncode != Window.CANCEL) {
for( UDIGEditorInputDescriptor desc : ApplicationGIS.getEditorInputs(element) ) {
IWorkbenchPage page = PlatformUI.getWorkbench().getActiveWorkbenchWindow()
.getActivePage();
IEditorPart editor = page.findEditor(desc.createInput(element));
if (editor != null)
page.closeEditor(editor, false);
}
List<ProjectElement> elements = element.getElements(ProjectElement.class);
for( ProjectElement projectElement : elements ) {
doDelete(projectElement, deleteFiles, returncode);
}
Project projectInternal = element.getProjectInternal();
if (projectInternal != null)
projectInternal.getElementsInternal().remove(element);
else {
Project project = findProject(element);
if (project != null)
project.getElementsInternal().remove(element);
}
Resource resource = element.eResource();
if (resource != null) {
resource.getContents().remove(element);
resource.unload();
}
if (deleteFiles) {
try {
if (resource == null) {
return;
}
String path = resource.getURI().toFileString();
resource.unload();
int lastIndexOf = path.lastIndexOf('/');
if (lastIndexOf == -1)
lastIndexOf = path.length();
path = path.substring(0, lastIndexOf);
final File file = new File(path);
deleteFile(file);
} catch (Exception e) {
ProjectUIPlugin.log("Error deleting project element file", e); //$NON-NLS-1$
}
}
}
}
private Project findProject( ProjectElement element ) {
List< ? extends Project> projects = ApplicationGISInternal.getProjects();
for( Project project : projects ) {
if (project.getElements().contains(element))
return project;
}
return null;
}
/**
* @see org.locationtech.udig.project.ui.UDIGGenericAction#operate(org.locationtech.udig.project.Project)
*/
@SuppressWarnings("unchecked")
@Override
protected void operate( Project project, Object context ) {
if (project == null || context == null){
return;
}
Pair<Boolean, Integer> pair = (Pair<Boolean, Integer>) context;
boolean deleteFiles = pair.left();
int returnCode = pair.right();
doDelete(project, deleteFiles, returnCode);
}
protected final void doDelete( Project project, boolean deleteProjectFiles, int returncode ) {
if (returncode != Window.CANCEL) {
Resource resource = project.eResource();
if (!deleteProjectFiles) {
try {
resource.save(null);
resource.getContents().remove(project);
} catch (IOException e) {
ProjectUIPlugin.log(null, e);
}
}
List<ProjectElement> toRemove = new ArrayList<ProjectElement>();
toRemove.addAll(project.getElementsInternal());
boolean oldrunSyn = runSync;
this.runSync = true;
for( ProjectElement element : toRemove ) {
doDelete(element, deleteProjectFiles, returncode);
}
runSync = oldrunSyn;
resource.setModified(false);
if (ApplicationGIS.getActiveProject() == project)
ProjectPlugin.getPlugin().getProjectRegistry().setCurrentProject(null);
ProjectPlugin.getPlugin().getProjectRegistry().getProjects().remove(project);
resource.getContents().clear();
ResourceSet resourceSet = resource.getResourceSet();
String path = resource.getURI().toFileString();
resource.unload();
if (deleteProjectFiles) {
try {
resourceSet.getResources().remove(resource);
resource.unload();
int lastIndexOf = path.lastIndexOf('/');
if (lastIndexOf == -1)
lastIndexOf = path.length();
path = path.substring(0, lastIndexOf);
final File file = new File(path);
deleteFile(file);
} catch (Exception e) {
ProjectUIPlugin.log("Error deleting project file", e); //$NON-NLS-1$
}
}
}
}
private void deleteFile( File file ) {
if (!file.exists())
return;
if (file.isDirectory()) {
File[] files = file.listFiles();
for( File file2 : files ) {
deleteFile(file2);
}
}
file.delete();
}
/**
* @see org.locationtech.udig.project.ui.UDIGGenericAction#operate(org.geotools.feature.SimpleFeature)
*/
@Override
protected void operate( final SimpleFeature feature, Object c ) {
IAdaptable adaptableFeature = null;
if (feature instanceof IAdaptable) {
adaptableFeature = (IAdaptable) feature;
}
if (adaptableFeature == null) {
adaptableFeature = (IAdaptable) Platform.getAdapterManager().getAdapter(feature,
IAdaptable.class);
}
if (adaptableFeature == null)
return;
final Layer layer = (Layer) adaptableFeature.getAdapter(Layer.class);
IDrawCommand command = DrawCommandFactory.getInstance().createDrawFeatureCommand(feature,
layer);
ViewportPane pane = (ViewportPane) layer.getMapInternal().getRenderManager()
.getMapDisplay();
pane.addDrawCommand(command);
PlatformGIS.syncInDisplayThread(PlatformUI.getWorkbench().getDisplay(), new Runnable(){
public void run() {
boolean result;
result = MessageDialog.openConfirm(PlatformUI.getWorkbench().getDisplay()
.getActiveShell(), Messages.DeleteFeature_confirmation_title,
Messages.DeleteFeature_confirmation_text);
if (result) {
UndoableMapCommand c = EditCommandFactory.getInstance().createDeleteFeature(
feature, layer);
executeCommand(c, layer.getMap());
}
}
});
command.setValid(false);
pane.repaint();
}
/**
* Determines whether the command executions should happen synchronously or not.
*
* @param runSync
*/
public void setRunSync( boolean runSync ) {
this.runSync = runSync;
}
private boolean getDoDelete() {
return ProjectPlugin.getPlugin().getPreferenceStore().getBoolean(
PreferenceConstants.P_PROJECT_DELETE_FILES);
}
private void setDoDelete( boolean deleteFiles ) {
ProjectPlugin.getPlugin().getPreferenceStore().setValue(
PreferenceConstants.P_PROJECT_DELETE_FILES, deleteFiles);
}
}