/*******************************************************************************
* Copyright (c) 2012 Obeo.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Obeo - initial API and implementation
*******************************************************************************/
package org.eclipse.mylyn.docs.intent.client.ui.editor.drop;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CancellationException;
import org.eclipse.core.runtime.preferences.IEclipsePreferences;
import org.eclipse.core.runtime.preferences.InstanceScope;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.mylyn.docs.intent.client.ui.IntentEditorActivator;
import org.eclipse.mylyn.docs.intent.client.ui.editor.IntentDocumentProvider;
import org.eclipse.mylyn.docs.intent.client.ui.editor.IntentEditor;
import org.eclipse.mylyn.docs.intent.client.ui.editor.IntentEditorDocument;
import org.eclipse.mylyn.docs.intent.client.ui.editor.renderers.IEditorRendererExtension;
import org.eclipse.mylyn.docs.intent.client.ui.internal.renderers.IEditorRendererExtensionRegistry;
import org.eclipse.mylyn.docs.intent.client.ui.preferences.IntentPreferenceConstants;
import org.eclipse.mylyn.docs.intent.client.ui.preferences.IntentPreferenceService;
import org.eclipse.mylyn.docs.intent.collab.common.logger.IIntentLogger.LogType;
import org.eclipse.mylyn.docs.intent.collab.common.logger.IntentLogger;
import org.eclipse.mylyn.docs.intent.collab.handlers.adapters.RepositoryAdapter;
import org.eclipse.mylyn.docs.intent.core.document.IntentSection;
import org.eclipse.mylyn.docs.intent.core.document.descriptionunit.DescriptionBloc;
import org.eclipse.mylyn.docs.intent.core.document.descriptionunit.DescriptionUnit;
import org.eclipse.mylyn.docs.intent.core.modelingunit.ModelingUnit;
import org.eclipse.mylyn.docs.intent.modelingunit.update.ExternalContentReferencesMergeUpdater;
import org.eclipse.mylyn.docs.intent.modelingunit.update.MergeUpdater;
import org.eclipse.swt.dnd.DropTargetAdapter;
import org.eclipse.swt.dnd.DropTargetEvent;
import org.eclipse.swt.widgets.Display;
/**
* Implementation of the drop support in intent editor.
*
* @author <a href="mailto:william.piers@obeo.fr">William Piers</a>
*/
public class IntentEditorDropSupport extends DropTargetAdapter {
/**
* The {@link IntentEditor} associated to this drop support.
*/
private IntentEditor editor;
/**
* The {@link IntentEditorDocument} associated to this drop support.
*/
private IntentEditorDocument document;
/**
* Constructor.
*
* @param editor
* the intent editor
*/
public IntentEditorDropSupport(IntentEditor editor) {
this.editor = editor;
this.document = (IntentEditorDocument)editor.getDocumentProvider().getDocument(
editor.getEditorInput());
}
/**
* {@inheritDoc}
*
* @see org.eclipse.swt.dnd.DropTargetAdapter#drop(org.eclipse.swt.dnd.DropTargetEvent)
*/
public void drop(DropTargetEvent event) {
// Step 1: get EObjects from drop event
List<EObject> droppedEObjects = getDroppedEObjectsFromEvent(event);
// Step 2: react to drop
if (!droppedEObjects.isEmpty()) {
IntentDocumentProvider documentProvider = (IntentDocumentProvider)editor.getDocumentProvider();
final RepositoryAdapter repositoryAdapter = documentProvider.getListenedElementsHandler()
.getRepositoryAdapter();
int carretOffset = editor.getProjectionViewer().getTextWidget().getCaretOffset();
// Step 2.1: get element at dropped offset (that will be the created elements previous sibling
EObject intentElement = document.getElementAtOffset(carretOffset);
EObject parent = intentElement;
try {
// if the dropped offset is an empty line, get the previous line
while (!(intentElement instanceof DescriptionBloc)
&& !((intentElement instanceof DescriptionUnit))
&& document
.get(carretOffset,
document.getLineLength(document.getLineOfOffset(carretOffset)))
.trim().length() < 1 && carretOffset > -1) {
carretOffset--;
}
intentElement = document.getElementAtOffset(carretOffset);
} catch (BadLocationException e1) {
// Silent catch
}
// get parent in which element will be created
while (parent != null && !(parent instanceof ModelingUnit || parent instanceof IntentSection)) {
parent = parent.eContainer();
}
try {
// Step 2.2: create elements
reactToDrop(repositoryAdapter, parent, intentElement, droppedEObjects);
document.reloadFromAST();
} catch (CancellationException e) {
// Nothing to do, drop was cancelled
}
}
}
/**
* Returns the EObjects corresponding to the given {@link DropTargetEvent}, using contributed
* {@link IEditorRendererExtension}s.
*
* @param event
* the {@link DropTargetEvent}
* @return the EObjects corresponding to the given {@link DropTargetEvent}
*/
private List<EObject> getDroppedEObjectsFromEvent(DropTargetEvent event) {
List<EObject> droppedEObjects = new ArrayList<EObject>();
// Default behavior : get EObjects from structured selection
if (event.data instanceof IStructuredSelection) {
for (Iterator<?> iterator = ((IStructuredSelection)event.data).iterator(); iterator.hasNext();) {
Object data = iterator.next();
if (data instanceof EObject) {
droppedEObjects.add((EObject)data);
} else {
// Delegate drop to IEditorRendererExtensions
for (IEditorRendererExtension editorRendererExtension : IEditorRendererExtensionRegistry
.getEditorRendererExtensions()) {
droppedEObjects.addAll(editorRendererExtension.getEObjectsFromDropTargetEvent(event));
}
}
}
} else {
// Delegate drop to IEditorRendererExtensions
for (IEditorRendererExtension editorRendererExtension : IEditorRendererExtensionRegistry
.getEditorRendererExtensions()) {
droppedEObjects.addAll(editorRendererExtension.getEObjectsFromDropTargetEvent(event));
}
}
return droppedEObjects;
}
/**
* Reacts to the drop by updating the {@link org.eclipse.mylyn.docs.intent.core.document.IntentDocument}.
*
* @param repositoryAdapter
* the repository adapter
* @param parent
* the parent {@link org.eclipse.mylyn.docs.intent.core.document.IntentDocument} element in
* which creating dropped elements
* @param sibling
* the Intent element located right before the elements to create
* @param droppedEObjects
* the dropped {@link EObject}s
* @throws CancellationException
* if the drop was cancelled
*/
private void reactToDrop(RepositoryAdapter repositoryAdapter, EObject parent, EObject sibling,
List<EObject> droppedEObjects) throws CancellationException {
boolean shouldUseExternalContentReferencesDropMode = shouldUseExternalContentReferencesDropMode();
// Step 1: display (if needed) a pop-up allowing end-user to choose which drop mode should be used
if (shouldDisplayDropModePopUp()) {
shouldUseExternalContentReferencesDropMode = displayDropModePopUp(shouldUseExternalContentReferencesDropMode);
}
// Step 2: perform drop
MergeUpdater updater = null;
if (shouldUseExternalContentReferencesDropMode) {
// Using External content references
updater = new ExternalContentReferencesMergeUpdater(repositoryAdapter);
} else {
// Using Merge Updater
updater = new MergeUpdater(repositoryAdapter);
}
if (parent instanceof ModelingUnit) {
updater.create((ModelingUnit)parent, sibling, droppedEObjects);
} else if (parent instanceof IntentSection) {
updater.create((IntentSection)parent, sibling, droppedEObjects);
} else {
IntentLogger.getInstance().log(LogType.ERROR,
"Can't drop external references in this container:" + parent);
}
}
/**
* Opens a drop mode pop-up letting the end-user choose which drop mode should be used .
*
* @param shouldUseExternalContentReferencesDropMode
* default preference value
* @return true if the end-user want to use external content reference, false otherwise
* @throws CancellationException
* if the end-user has cancelled
*/
private boolean displayDropModePopUp(boolean shouldUseExternalContentReferencesDropMode)
throws CancellationException {
// Open dialog
DropModeDialog dropModeDialog = new DropModeDialog(Display.getCurrent().getActiveShell());
dropModeDialog.open();
// Get end-user choice
if (dropModeDialog.isCancelled()) {
throw new CancellationException();
}
// Update preferences
IEclipsePreferences node = InstanceScope.INSTANCE.getNode(IntentEditorActivator.getDefault()
.getBundle().getSymbolicName());
node.putBoolean(IntentPreferenceConstants.DND_DISPLAY_POP_UP, !dropModeDialog.getToggleState());
node.putBoolean(IntentPreferenceConstants.DND_USE_EXTERNAL_REFERENCES,
dropModeDialog.shouldUseExternalReferences());
return dropModeDialog.shouldUseExternalReferences();
}
/**
* Indicates whether we should display a pop-up letting the end-user choose which drop mode should be
* used.
*
* @return true if the pop-up should be displayed, false otherwise
*/
private boolean shouldDisplayDropModePopUp() {
return IntentPreferenceService.getBoolean(IntentPreferenceConstants.DND_DISPLAY_POP_UP);
}
/**
* Indicates whether we should use external content references to link the dropped elements with the
* {@link org.eclipse.mylyn.docs.intent.core.document.IntentDocument}, according to preferences.
*
* @return true if we should use external content references to link the dropped elements, false otherwise
*/
private boolean shouldUseExternalContentReferencesDropMode() {
return IntentPreferenceService.getBoolean(IntentPreferenceConstants.DND_USE_EXTERNAL_REFERENCES);
}
}