Bug 519770 - Model editor should support copy of multiple elements



Change-Id: Ice5500c5e6b796d484e564d7ee2fb618761ae70e
Signed-off-by: Erdal Karaca <erdal.karaca.de@googlemail.com>
diff --git a/bundles/org.eclipse.e4.tools.emf.ui/src/org/eclipse/e4/tools/emf/ui/internal/common/ModelEditor.java b/bundles/org.eclipse.e4.tools.emf.ui/src/org/eclipse/e4/tools/emf/ui/internal/common/ModelEditor.java
index 6fd042a..783e877 100644
--- a/bundles/org.eclipse.e4.tools.emf.ui/src/org/eclipse/e4/tools/emf/ui/internal/common/ModelEditor.java
+++ b/bundles/org.eclipse.e4.tools.emf.ui/src/org/eclipse/e4/tools/emf/ui/internal/common/ModelEditor.java
@@ -170,6 +170,7 @@
 import org.eclipse.e4.ui.model.application.ui.menu.impl.MenuPackageImpl;
 import org.eclipse.e4.ui.model.fragment.MModelFragment;
 import org.eclipse.e4.ui.model.fragment.MModelFragments;
+import org.eclipse.e4.ui.model.fragment.MStringModelFragment;
 import org.eclipse.e4.ui.model.fragment.impl.FragmentPackageImpl;
 import org.eclipse.e4.ui.model.internal.ModelUtils;
 import org.eclipse.e4.ui.services.IServiceConstants;
@@ -177,6 +178,7 @@
 import org.eclipse.e4.ui.workbench.UIEvents.EventTags;
 import org.eclipse.e4.ui.workbench.modeling.ESelectionService;
 import org.eclipse.emf.common.command.Command;
+import org.eclipse.emf.common.command.CompoundCommand;
 import org.eclipse.emf.databinding.EMFProperties;
 import org.eclipse.emf.databinding.FeaturePath;
 import org.eclipse.emf.databinding.IEMFProperty;
@@ -306,7 +308,6 @@
 	/** An imageRegistry for dynamic component images (see bug #403583) */
 	private final ImageRegistry componentImages = new ImageRegistry();
 
-
 	@Inject
 	ESelectionService selectionService;
 
@@ -534,8 +535,8 @@
 
 				if (o instanceof MPart) {
 					System.err.println(getClass().getName() + ".findAndHighLight: " + o); //$NON-NLS-1$
-					System.err.println(getClass().getName()
-							+ ".findAndHighLight: " + ((EObject) o).eContainingFeature()); //$NON-NLS-1$
+					System.err
+					.println(getClass().getName() + ".findAndHighLight: " + ((EObject) o).eContainingFeature()); //$NON-NLS-1$
 				}
 
 				viewer.setSelection(new StructuredSelection(o));
@@ -693,8 +694,8 @@
 				if (!s.isEmpty() && noSelected == 1) {
 					List<Action> actions;
 					if (s.getFirstElement() instanceof VirtualEntry<?>) {
-						actions = virtualEditors.get(((VirtualEntry<?>) s.getFirstElement()).getId()).getActions(
-								s.getFirstElement());
+						actions = virtualEditors.get(((VirtualEntry<?>) s.getFirstElement()).getId())
+								.getActions(s.getFirstElement());
 						if (actions.size() > 0) {
 							final MenuManager addMenu = new MenuManager(messages.ModelEditor_AddChild);
 							for (final Action a : actions) {
@@ -704,8 +705,8 @@
 							manager.add(addMenu);
 						}
 
-						actions = virtualEditors.get(((VirtualEntry<?>) s.getFirstElement()).getId()).getActionsImport(
-								s.getFirstElement());
+						actions = virtualEditors.get(((VirtualEntry<?>) s.getFirstElement()).getId())
+								.getActionsImport(s.getFirstElement());
 						if (actions.size() > 0) {
 							final MenuManager menu = new MenuManager(messages.ModelEditor_Import3x);
 							for (final Action a : actions) {
@@ -759,8 +760,8 @@
 
 						if (o.eContainer() != null) {
 							addSeparator = true;
-							manager.add(new Action(messages.ModelEditor_Delete, ImageDescriptor
-									.createFromImage(resourcePool.getImageUnchecked(ResourceProvider.IMG_Obj16_cross))) {
+							manager.add(new Action(messages.ModelEditor_Delete, ImageDescriptor.createFromImage(
+									resourcePool.getImageUnchecked(ResourceProvider.IMG_Obj16_cross))) {
 								@Override
 								public void run() {
 									final Command cmd = DeleteCommand.create(modelProvider.getEditingDomain(), o);
@@ -878,8 +879,8 @@
 					final Action nlsAction = new Action(messages.ModelEditor_ExternalizeStrings) {
 						@Override
 						public void run() {
-							final ExternalizeStringHandler h = ContextInjectionFactory.make(
-									ExternalizeStringHandler.class, context);
+							final ExternalizeStringHandler h = ContextInjectionFactory
+									.make(ExternalizeStringHandler.class, context);
 							ContextInjectionFactory.invoke(h, Execute.class, context);
 						}
 					};
@@ -1105,8 +1106,8 @@
 		final String property = System
 				.getProperty(ORG_ECLIPSE_E4_TOOLS_MODELEDITOR_FILTEREDTREE_ENABLED_XMITAB_DISABLED);
 		if (property != null || preferences.getBoolean(ModelEditorPreferences.TAB_FORM_SEARCH_SHOW, false)) {
-			final FilteredTree viewParent = new FilteredTree(treeArea, SWT.MULTI | SWT.FULL_SELECTION | SWT.H_SCROLL
-					| SWT.V_SCROLL, new PatternFilter(true));
+			final FilteredTree viewParent = new FilteredTree(treeArea,
+					SWT.MULTI | SWT.FULL_SELECTION | SWT.H_SCROLL | SWT.V_SCROLL, new PatternFilter(true));
 			tempViewer = viewParent.getViewer();
 		} else {
 			tempViewer = new TreeViewerEx(treeArea, SWT.MULTI | SWT.FULL_SELECTION | SWT.H_SCROLL | SWT.V_SCROLL,
@@ -1187,8 +1188,8 @@
 
 		final int ops = DND.DROP_MOVE;
 		viewer.addDragSupport(ops, new Transfer[] { MemoryTransfer.getInstance() }, new DragListener(viewer));
-		viewer.addDropSupport(ops, new Transfer[] { MemoryTransfer.getInstance() }, new DropListener(viewer,
-				modelProvider.getEditingDomain()));
+		viewer.addDropSupport(ops, new Transfer[] { MemoryTransfer.getInstance() },
+				new DropListener(viewer, modelProvider.getEditingDomain()));
 
 		return viewer;
 	}
@@ -1203,8 +1204,8 @@
 			}
 
 			final IContributionFactory fact = context.get(IContributionFactory.class);
-			final AbstractComponentEditor editor = (AbstractComponentEditor) fact.create(
-					"bundleclass://" + el.getContributor().getName() + "/" + el.getAttribute("class"), context); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+			final AbstractComponentEditor editor = (AbstractComponentEditor) fact
+					.create("bundleclass://" + el.getContributor().getName() + "/" + el.getAttribute("class"), context); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
 			registerVirtualEditor(el.getAttribute("id"), editor); //$NON-NLS-1$
 		}
 	}
@@ -1216,7 +1217,8 @@
 		registerVirtualEditor(VIRTUAL_COMMAND, ContextInjectionFactory.make(VCommandEditor.class, context));
 		registerVirtualEditor(VIRTUAL_APPLICATION_WINDOWS,
 				ContextInjectionFactory.make(VApplicationWindowEditor.class, context));
-		registerVirtualEditor(VIRTUAL_WINDOW_WINDOWS, ContextInjectionFactory.make(VWindowWindowsEditor.class, context));
+		registerVirtualEditor(VIRTUAL_WINDOW_WINDOWS,
+				ContextInjectionFactory.make(VWindowWindowsEditor.class, context));
 		registerVirtualEditor(VIRTUAL_PERSPECTIVE_WINDOWS,
 				ContextInjectionFactory.make(VPerspectiveWindowsEditor.class, context));
 		registerVirtualEditor(VIRTUAL_WINDOW_CONTROLS,
@@ -1270,7 +1272,8 @@
 				final EClass eClass = desc.getEClass();
 				final IContributionFactory fact = context.get(IContributionFactory.class);
 				final AbstractComponentEditor editor = (AbstractComponentEditor) fact.create(
-						"bundleclass://" + el.getContributor().getName() + "/" + desc.getEditorClass().getName(), context); //$NON-NLS-1$ //$NON-NLS-2$
+						"bundleclass://" + el.getContributor().getName() + "/" + desc.getEditorClass().getName(), //$NON-NLS-1$ //$NON-NLS-2$
+						context);
 				registerEditor(eClass, editor);
 			} catch (final CoreException e) {
 				e.printStackTrace();
@@ -1318,16 +1321,20 @@
 	}
 
 	private void registerDefaultEditors() {
-		// System.err.println(getClass().getName() + ".registerDefaultEditors: " + resourcePool); //$NON-NLS-1$
+		// System.err.println(getClass().getName() + ".registerDefaultEditors: " +
+		// resourcePool); //$NON-NLS-1$
 
 		registerEditor(ApplicationPackageImpl.Literals.APPLICATION,
 				ContextInjectionFactory.make(ApplicationEditor.class, context));
-		registerEditor(ApplicationPackageImpl.Literals.ADDON, ContextInjectionFactory.make(AddonsEditor.class, context));
+		registerEditor(ApplicationPackageImpl.Literals.ADDON,
+				ContextInjectionFactory.make(AddonsEditor.class, context));
 
 		registerEditor(CommandsPackageImpl.Literals.KEY_BINDING,
 				ContextInjectionFactory.make(KeyBindingEditor.class, context));
-		registerEditor(CommandsPackageImpl.Literals.HANDLER, ContextInjectionFactory.make(HandlerEditor.class, context));
-		registerEditor(CommandsPackageImpl.Literals.COMMAND, ContextInjectionFactory.make(CommandEditor.class, context));
+		registerEditor(CommandsPackageImpl.Literals.HANDLER,
+				ContextInjectionFactory.make(HandlerEditor.class, context));
+		registerEditor(CommandsPackageImpl.Literals.COMMAND,
+				ContextInjectionFactory.make(CommandEditor.class, context));
 		registerEditor(CommandsPackageImpl.Literals.COMMAND_PARAMETER,
 				ContextInjectionFactory.make(CommandParameterEditor.class, context));
 		registerEditor(CommandsPackageImpl.Literals.PARAMETER,
@@ -1462,8 +1469,8 @@
 	@Inject
 	public void setNotVisibleRenderedColor(
 			@Preference(ModelEditorPreferences.NOT_VISIBLE_AND_RENDERED_COLOR) String color) {
-		final RGB current = JFaceResources.getColorRegistry().getRGB(
-				ComponentLabelProvider.NOT_VISIBLE_AND_RENDERED_KEY);
+		final RGB current = JFaceResources.getColorRegistry()
+				.getRGB(ComponentLabelProvider.NOT_VISIBLE_AND_RENDERED_KEY);
 
 		if (current == null || !current.equals(color)) {
 			JFaceResources.getColorRegistry().put(ComponentLabelProvider.NOT_VISIBLE_AND_RENDERED_KEY,
@@ -1599,15 +1606,29 @@
 		@SuppressWarnings("unchecked")
 		private void handleStructurePaste() {
 			final Clipboard clip = new Clipboard(viewer.getControl().getDisplay());
-			Object o = clip.getContents(MemoryTransfer.getInstance());
+			Object contents = clip.getContents(MemoryTransfer.getInstance());
 			clip.dispose();
-			if (o == null) {
+			if (contents == null) {
+				return;
+			}
+			List<EObject> toCopy = new ArrayList<>();
+			if (contents instanceof EObject) {
+				toCopy.add(EcoreUtil.copy((EObject) contents));
+			} else if (contents instanceof List<?>) {
+				List<Object> list = (List<Object>) contents;
+				for (Object object : list) {
+					if (object instanceof EObject) {
+						toCopy.add(EcoreUtil.copy((EObject) object));
+					}
+				}
+			}
+
+			if (toCopy.isEmpty()) {
 				return;
 			}
 
-			o = EcoreUtil.copy((EObject) o);
-
 			final Object parent = ((IStructuredSelection) viewer.getSelection()).getFirstElement();
+			EObject probe = toCopy.get(0);
 
 			EStructuralFeature feature = null;
 			EObject container = null;
@@ -1615,44 +1636,98 @@
 				final VirtualEntry<?> v = (VirtualEntry<?>) parent;
 				feature = ((IEMFProperty) v.getProperty()).getStructuralFeature();
 				container = (EObject) v.getOriginalParent();
-			} else {
-				if (parent instanceof MElementContainer<?>) {
+			} else if (parent instanceof EObject) {
+				container = (EObject) parent;
+				if (container instanceof MElementContainer<?>) {
 					feature = UiPackageImpl.Literals.ELEMENT_CONTAINER__CHILDREN;
-					container = (EObject) parent;
-				} else if (parent instanceof EObject) {
-					container = (EObject) parent;
-					final EClass eClass = container.eClass();
-
-					for (final EStructuralFeature f : eClass.getEAllStructuralFeatures()) {
-						if (ModelUtils.getTypeArgument(eClass, f.getEGenericType()).isInstance(o)) {
-							feature = f;
-							break;
-						}
+				} else {
+					feature = determineTargetFeature(probe, container);
+					if (feature == null && container.eClass().equals(probe.eClass())
+							&& container.eContainer() != null) {
+						// it seems the user has still the original selection active,
+						// try to find the target feature using the container's container
+						container = container.eContainer();
+						feature = determineTargetFeature(probe, container);
 					}
 				}
 			}
 
-			if (feature == FragmentPackageImpl.Literals.MODEL_FRAGMENTS__IMPORTS && container != null) {
-				final MApplicationElement el = (MApplicationElement) EcoreUtil.create(((EObject) o).eClass());
-				el.setElementId(((MApplicationElement) o).getElementId());
-				final Command cmd = AddCommand.create(getModelProvider().getEditingDomain(), container, feature, el);
-				if (cmd.canExecute()) {
-					getModelProvider().getEditingDomain().getCommandStack().execute(cmd);
-				}
+			if (container == null) {
+				// no container selected that we can paste into
 				return;
 			}
 
-			if (feature != null && container != null) {
-				final Command cmd = AddCommand.create(getModelProvider().getEditingDomain(), container, feature, o);
-				if (cmd.canExecute()) {
-					getModelProvider().getEditingDomain().getCommandStack().execute(cmd);
-					if (isLiveModel()) {
-						if (container instanceof MElementContainer<?> && o instanceof MUIElement) {
-							((MElementContainer<MUIElement>) container).setSelectedElement((MUIElement) o);
+			if (feature == null) {
+				// no target feature derivable from current state of the editor
+				return;
+			}
+
+			List<EClass> targetChildrenClasses = new ArrayList<>();
+
+			if (container instanceof MStringModelFragment) {
+				MStringModelFragment stringModelFragment = (MStringModelFragment) container;
+				EClass targetType = StringModelFragment.findContainerType(stringModelFragment);
+				if (targetType != null) {
+					EStructuralFeature targetFeature = targetType
+							.getEStructuralFeature(stringModelFragment.getFeaturename());
+					if (targetFeature != null) {
+						List<FeatureClass> classes = StringModelFragment.getTargetChildrenClasses(targetType,
+								targetFeature.getName());
+						for (FeatureClass fclass : classes) {
+							targetChildrenClasses.add(fclass.eClass);
 						}
 					}
 				}
 			}
+
+			CompoundCommand cc = new CompoundCommand();
+			for (EObject eObject : toCopy) {
+				if (!ModelUtils.getTypeArgument(eObject.eClass(), feature.getEGenericType()).isInstance(eObject)) {
+					// the object to paste does not fit into the target feature
+					continue;
+				}
+				if (!targetChildrenClasses.isEmpty() && !targetChildrenClasses.contains(eObject.eClass())) {
+					// there is a limited list of allowed target types
+					// and a fragment that is pointing to a different feature
+					continue;
+				}
+
+				if (feature == FragmentPackageImpl.Literals.MODEL_FRAGMENTS__IMPORTS) {
+					final MApplicationElement el = (MApplicationElement) EcoreUtil.create(eObject.eClass());
+					el.setElementId(((MApplicationElement) eObject).getElementId());
+					final Command cmd = AddCommand.create(getModelProvider().getEditingDomain(), container, feature,
+							el);
+					if (cmd.canExecute()) {
+						cc.append(cmd);
+					}
+					return;
+				}
+
+				final Command cmd = AddCommand.create(getModelProvider().getEditingDomain(), container, feature,
+						eObject);
+				if (cmd.canExecute()) {
+					cc.append(cmd);
+					if (isLiveModel()) {
+						if (container instanceof MElementContainer<?> && probe instanceof MUIElement) {
+							// the last selection wins
+							((MElementContainer<MUIElement>) container).setSelectedElement((MUIElement) eObject);
+						}
+					}
+				}
+			}
+			if (!cc.isEmpty()) {
+				getModelProvider().getEditingDomain().getCommandStack().execute(cc);
+			}
+		}
+
+		private EStructuralFeature determineTargetFeature(EObject probe, EObject container) {
+			final EClass eClass = container.eClass();
+			for (final EStructuralFeature f : eClass.getEAllReferences()) {
+				if (ModelUtils.getTypeArgument(eClass, f.getEGenericType()).isInstance(probe)) {
+					return f;
+				}
+			}
+			return null;
 		}
 
 		@Override
@@ -1669,13 +1744,20 @@
 		}
 
 		private void handleStructureCopy() {
-			final Object o = ((IStructuredSelection) viewer.getSelection()).getFirstElement();
-			if (o != null && o instanceof EObject) {
-				final Clipboard clip = new Clipboard(viewer.getControl().getDisplay());
-				clip.setContents(new Object[] { EcoreUtil.copy((EObject) o) },
-						new Transfer[] { MemoryTransfer.getInstance() });
-				clip.dispose();
+			IStructuredSelection structuredSelection = (IStructuredSelection) viewer.getSelection();
+			List<EObject> toCopy = new ArrayList<>();
+			for (Object obj : structuredSelection.toList()) {
+				if (obj != null && obj instanceof EObject) {
+					EObject copy = EcoreUtil.copy((EObject) obj);
+					toCopy.add(copy);
+				}
 			}
+			if (toCopy.isEmpty()) {
+				return;
+			}
+			final Clipboard clip = new Clipboard(viewer.getControl().getDisplay());
+			clip.setContents(new Object[] { toCopy }, new Transfer[] { MemoryTransfer.getInstance() });
+			clip.dispose();
 		}
 
 		@Override
@@ -2003,11 +2085,9 @@
 		return null;
 	}
 
-
 	@Inject
 	@Optional
-	public void refreshOnSave(
-			@UIEventTopic(UIEvents.Dirtyable.TOPIC_DIRTY) org.osgi.service.event.Event event,
+	public void refreshOnSave(@UIEventTopic(UIEvents.Dirtyable.TOPIC_DIRTY) org.osgi.service.event.Event event,
 			@Named(IServiceConstants.ACTIVE_PART) MPart part) {
 		// If the application model is saved (-> becomes undirty) we must
 		// refresh tree (bug 472706)
diff --git a/bundles/org.eclipse.e4.tools.emf.ui/src/org/eclipse/e4/tools/emf/ui/internal/common/component/StringModelFragment.java b/bundles/org.eclipse.e4.tools.emf.ui/src/org/eclipse/e4/tools/emf/ui/internal/common/component/StringModelFragment.java
index 5b6c259..787a9d4 100644
--- a/bundles/org.eclipse.e4.tools.emf.ui/src/org/eclipse/e4/tools/emf/ui/internal/common/component/StringModelFragment.java
+++ b/bundles/org.eclipse.e4.tools.emf.ui/src/org/eclipse/e4/tools/emf/ui/internal/common/component/StringModelFragment.java
@@ -188,13 +188,28 @@
 	 * @return
 	 */
 	private EClass getSelectedContainer() {
-
 		if (selectedContainer != null) {
 			return selectedContainer;
 		}
 
 		// we get the StringModelFragment. If not initialized, no search...
 		StringModelFragmentImpl modelFragment = getStringModelFragment();
+		selectedContainer = findContainerType(modelFragment);
+
+		updateTitle();
+
+		return selectedContainer;
+	}
+
+	/**
+	 * Returns the selectedContainer, which is the EClass behind the Extended
+	 * Element ID. It can be known thanks to the dialog or must be computed from the
+	 * ID value
+	 *
+	 * @return
+	 */
+	public static EClass findContainerType(MStringModelFragment modelFragment) {
+		// we get the StringModelFragment. If not initialized, no search...
 		if (modelFragment == null) {
 			return null;
 		}
@@ -207,8 +222,7 @@
 
 		// known ID for application are directly filtered.
 		if ("xpath:/".equals(parentElementId) || "org.eclipse.e4.legacy.ide.application".equals(parentElementId)) {
-			selectedContainer = ApplicationPackageImpl.eINSTANCE.getApplication();
-			return selectedContainer;
+			return ApplicationPackageImpl.eINSTANCE.getApplication();
 		}
 
 		// We have to proceed to a simple search on all elements in all resource
@@ -230,8 +244,7 @@
 					if (o instanceof MApplication) {
 						EClass found = getTargetClassFromXPath((MApplication) o, xpath);
 						if (found != null) {
-							selectedContainer = found;
-							return selectedContainer;
+							return found;
 						}
 					}
 				} else {
@@ -239,16 +252,13 @@
 					if ((o instanceof MApplicationElement)
 							&& (o.eContainingFeature() != FragmentPackageImpl.Literals.MODEL_FRAGMENTS__IMPORTS)
 							&& parentElementId.equals(((MApplicationElement) o).getElementId())) {
-						selectedContainer = o.eClass();
-						return selectedContainer;
+						return o.eClass();
 					}
 				}
 			}
 		}
 
-		updateTitle();
-
-		return selectedContainer;
+		return null;
 	}
 
 	private void updateTitle() {
@@ -605,7 +615,7 @@
 	 *            : the xpath value without the 'xpath:' prefix
 	 * @return the list of EClass(es) matching this xpath
 	 */
-	private EClass getTargetClassFromXPath(MApplication application, String xpath) {
+	private static EClass getTargetClassFromXPath(MApplication application, String xpath) {
 
 		XPathContextFactory<EObject> f = EcoreXPathContextFactory.newInstance();
 		XPathContext xpathContext = f.newContext((EObject) application);
@@ -636,16 +646,35 @@
 	 */
 
 	public List<FeatureClass> getTargetChildrenClasses() {
+		List<FeatureClass> targetChildrenClasses = new ArrayList<>();
+		if (selectedContainer != null) {
+			List<FeatureClass> childTypes = getTargetChildrenClasses(selectedContainer,
+					getStringModelFragment().getFeaturename());
+			targetChildrenClasses.addAll(childTypes);
+		}
+		return targetChildrenClasses;
+	}
+
+	/**
+	 * This method computes the available classes that can be selected as child for
+	 * the current selected element. The result is cached in a map as the meta model
+	 * will not change !
+	 *
+	 * @param targetClass
+	 *            the target class to check against
+	 *
+	 * @return an empty list or the list for possible children
+	 */
+
+	public static List<FeatureClass> getTargetChildrenClasses(EClass targetClass, String featurename) {
 		List<FeatureClass> result = Collections.emptyList();
 
-		if (selectedContainer != null) {
-
+		if (targetClass != null) {
 			// The top level class for children, is the class of the EReference
 			// bound to feature name
 			EReference childRef = null;
-			String featurename = getStringModelFragment().getFeaturename();
 
-			for (EReference ref : selectedContainer.getEAllReferences()) {
+			for (EReference ref : targetClass.getEAllReferences()) {
 				if (ref.getName().equals(featurename)) {
 					childRef = ref;
 					break;
@@ -661,7 +690,7 @@
 			// UIElementContainer<T extends UIElement>
 			// We must check if the selectedContainer extends
 			// UIElementContainer<XXX> and in this case childRef is XXX
-			final EClass childClass = (EClass) ModelUtils.getTypeArgument(selectedContainer,
+			final EClass childClass = (EClass) ModelUtils.getTypeArgument(targetClass,
 					childRef.getEGenericType());
 
 			// Search for descendant of ChildClass -> This result could be
@@ -678,7 +707,6 @@
 			}
 		}
 		return result;
-
 	}
 
 }