blob: 3fe98bb234af746b63abb93cce886ba9a3ec1a2a [file] [log] [blame]
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Eclipse Public License, Version 1.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.eclipse.org/org/documents/epl-v10.php
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.ide.eclipse.adt.internal.editors.ui.tree;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
import org.apache.xml.serialize.Method;
import org.apache.xml.serialize.OutputFormat;
import org.apache.xml.serialize.XMLSerializer;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.swt.dnd.Clipboard;
import org.eclipse.swt.dnd.TextTransfer;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.ui.ISharedImages;
import org.eclipse.ui.PlatformUI;
import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion;
import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
import org.eclipse.wst.xml.core.internal.document.NodeContainer;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import java.io.IOException;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;
/**
* Provides Cut and Copy actions for the tree nodes.
*/
@SuppressWarnings({"restriction", "deprecation"})
public class CopyCutAction extends Action {
private List<UiElementNode> mUiNodes;
private boolean mPerformCut;
private final AndroidXmlEditor mEditor;
private final Clipboard mClipboard;
private final ICommitXml mXmlCommit;
/**
* Creates a new Copy or Cut action.
*
* @param selected The UI node to cut or copy. It *must* have a non-null XML node.
* @param performCut True if the operation is cut, false if it is copy.
*/
public CopyCutAction(AndroidXmlEditor editor, Clipboard clipboard, ICommitXml xmlCommit,
UiElementNode selected, boolean performCut) {
this(editor, clipboard, xmlCommit, toList(selected), performCut);
}
/**
* Creates a new Copy or Cut action.
*
* @param selected The UI nodes to cut or copy. They *must* have a non-null XML node.
* The list becomes owned by the {@link CopyCutAction}.
* @param performCut True if the operation is cut, false if it is copy.
*/
public CopyCutAction(AndroidXmlEditor editor, Clipboard clipboard, ICommitXml xmlCommit,
List<UiElementNode> selected, boolean performCut) {
super(performCut ? "Cut" : "Copy");
mEditor = editor;
mClipboard = clipboard;
mXmlCommit = xmlCommit;
ISharedImages images = PlatformUI.getWorkbench().getSharedImages();
if (performCut) {
setImageDescriptor(images.getImageDescriptor(ISharedImages.IMG_TOOL_CUT));
setHoverImageDescriptor(images.getImageDescriptor(ISharedImages.IMG_TOOL_CUT_HOVER));
setDisabledImageDescriptor(
images.getImageDescriptor(ISharedImages.IMG_TOOL_CUT_DISABLED));
} else {
setImageDescriptor(images.getImageDescriptor(ISharedImages.IMG_TOOL_COPY));
setHoverImageDescriptor(images.getImageDescriptor(ISharedImages.IMG_TOOL_COPY_HOVER));
setDisabledImageDescriptor(
images.getImageDescriptor(ISharedImages.IMG_TOOL_COPY_DISABLED));
}
mUiNodes = selected;
mPerformCut = performCut;
}
/**
* Performs the cut or copy action.
* First an XML serializer is used to turn the existing XML node into a valid
* XML fragment, which is added as text to the clipboard.
*/
@Override
public void run() {
super.run();
if (mUiNodes == null || mUiNodes.size() < 1) {
return;
}
// Commit the current pages first, to make sure the XML is in sync.
// Committing may change the XML structure.
if (mXmlCommit != null) {
mXmlCommit.commitPendingXmlChanges();
}
StringBuilder allText = new StringBuilder();
ArrayList<UiElementNode> nodesToCut = mPerformCut ? new ArrayList<UiElementNode>() : null;
for (UiElementNode uiNode : mUiNodes) {
try {
Node xml_node = uiNode.getXmlNode();
if (xml_node == null) {
return;
}
String data = getXmlTextFromEditor(xml_node);
// In the unlikely event that IStructuredDocument failed to extract the text
// directly from the editor, try to fall back on a direct XML serialization
// of the XML node. This uses the generic Node interface with no SSE tricks.
if (data == null) {
data = getXmlTextFromSerialization(xml_node);
}
if (data != null) {
allText.append(data);
if (mPerformCut) {
// only remove notes to cut if we actually got some XML text from them
nodesToCut.add(uiNode);
}
}
} catch (Exception e) {
AdtPlugin.log(e, "CopyCutAction failed for UI node %1$s", //$NON-NLS-1$
uiNode.getBreadcrumbTrailDescription(true));
}
} // for uiNode
if (allText != null && allText.length() > 0) {
mClipboard.setContents(
new Object[] { allText.toString() },
new Transfer[] { TextTransfer.getInstance() });
if (mPerformCut) {
for (UiElementNode uiNode : nodesToCut) {
uiNode.deleteXmlNode();
}
}
}
}
/** Get the data directly from the editor. */
private String getXmlTextFromEditor(Node xml_node) {
String data = null;
IStructuredModel model = mEditor.getModelForRead();
try {
IStructuredDocument sse_doc = mEditor.getStructuredDocument();
if (xml_node instanceof NodeContainer) {
// The easy way to get the source of an SSE XML node.
data = ((NodeContainer) xml_node).getSource();
} else if (xml_node instanceof IndexedRegion && sse_doc != null) {
// Try harder.
IndexedRegion region = (IndexedRegion) xml_node;
int start = region.getStartOffset();
int end = region.getEndOffset();
if (end > start) {
data = sse_doc.get(start, end - start);
}
}
} catch (BadLocationException e) {
// the region offset was invalid. ignore.
} finally {
model.releaseFromRead();
}
return data;
}
/**
* Direct XML serialization of the XML node.
* <p/>
* This uses the generic Node interface with no SSE tricks. It's however slower
* and doesn't respect formatting (since serialization is involved instead of reading
* the actual text buffer.)
*/
private String getXmlTextFromSerialization(Node xml_node) throws IOException {
String data;
StringWriter sw = new StringWriter();
XMLSerializer serializer = new XMLSerializer(sw,
new OutputFormat(Method.XML,
OutputFormat.Defaults.Encoding /* utf-8 */,
true /* indent */));
// Serialize will throw an IOException if it fails.
serializer.serialize((Element) xml_node);
data = sw.toString();
return data;
}
/**
* Static helper class to wrap on node into a list for the constructors.
*/
private static ArrayList<UiElementNode> toList(UiElementNode selected) {
ArrayList<UiElementNode> list = null;
if (selected != null) {
list = new ArrayList<UiElementNode>(1);
list.add(selected);
}
return list;
}
}