• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2008 The Android Open Source Project
3  *
4  * Licensed under the Eclipse Public License, Version 1.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.eclipse.org/org/documents/epl-v10.php
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.ide.eclipse.adt.internal.editors.ui.tree;
18 
19 import com.android.ide.eclipse.adt.AdtPlugin;
20 import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
21 import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
22 
23 import org.apache.xml.serialize.Method;
24 import org.apache.xml.serialize.OutputFormat;
25 import org.apache.xml.serialize.XMLSerializer;
26 import org.eclipse.jface.action.Action;
27 import org.eclipse.jface.text.BadLocationException;
28 import org.eclipse.swt.dnd.Clipboard;
29 import org.eclipse.swt.dnd.TextTransfer;
30 import org.eclipse.swt.dnd.Transfer;
31 import org.eclipse.ui.ISharedImages;
32 import org.eclipse.ui.PlatformUI;
33 import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
34 import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion;
35 import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
36 import org.eclipse.wst.xml.core.internal.document.NodeContainer;
37 import org.w3c.dom.Element;
38 import org.w3c.dom.Node;
39 
40 import java.io.IOException;
41 import java.io.StringWriter;
42 import java.util.ArrayList;
43 import java.util.List;
44 
45 
46 /**
47  * Provides Cut and Copy actions for the tree nodes.
48  */
49 public class CopyCutAction extends Action {
50     private List<UiElementNode> mUiNodes;
51     private boolean mPerformCut;
52     private final AndroidXmlEditor mEditor;
53     private final Clipboard mClipboard;
54     private final ICommitXml mXmlCommit;
55 
56     /**
57      * Creates a new Copy or Cut action.
58      *
59      * @param selected The UI node to cut or copy. It *must* have a non-null XML node.
60      * @param performCut True if the operation is cut, false if it is copy.
61      */
CopyCutAction(AndroidXmlEditor editor, Clipboard clipboard, ICommitXml xmlCommit, UiElementNode selected, boolean performCut)62     public CopyCutAction(AndroidXmlEditor editor, Clipboard clipboard, ICommitXml xmlCommit,
63             UiElementNode selected, boolean performCut) {
64         this(editor, clipboard, xmlCommit, toList(selected), performCut);
65     }
66 
67     /**
68      * Creates a new Copy or Cut action.
69      *
70      * @param selected The UI nodes to cut or copy. They *must* have a non-null XML node.
71      *                 The list becomes owned by the {@link CopyCutAction}.
72      * @param performCut True if the operation is cut, false if it is copy.
73      */
CopyCutAction(AndroidXmlEditor editor, Clipboard clipboard, ICommitXml xmlCommit, List<UiElementNode> selected, boolean performCut)74     public CopyCutAction(AndroidXmlEditor editor, Clipboard clipboard, ICommitXml xmlCommit,
75             List<UiElementNode> selected, boolean performCut) {
76         super(performCut ? "Cut" : "Copy");
77         mEditor = editor;
78         mClipboard = clipboard;
79         mXmlCommit = xmlCommit;
80 
81         ISharedImages images = PlatformUI.getWorkbench().getSharedImages();
82         if (performCut) {
83             setImageDescriptor(images.getImageDescriptor(ISharedImages.IMG_TOOL_CUT));
84             setHoverImageDescriptor(images.getImageDescriptor(ISharedImages.IMG_TOOL_CUT));
85             setDisabledImageDescriptor(
86                     images.getImageDescriptor(ISharedImages.IMG_TOOL_CUT_DISABLED));
87         } else {
88             setImageDescriptor(images.getImageDescriptor(ISharedImages.IMG_TOOL_COPY));
89             setHoverImageDescriptor(images.getImageDescriptor(ISharedImages.IMG_TOOL_COPY));
90             setDisabledImageDescriptor(
91                     images.getImageDescriptor(ISharedImages.IMG_TOOL_COPY_DISABLED));
92         }
93 
94         mUiNodes = selected;
95         mPerformCut = performCut;
96     }
97 
98     /**
99      * Performs the cut or copy action.
100      * First an XML serializer is used to turn the existing XML node into a valid
101      * XML fragment, which is added as text to the clipboard.
102      */
103     @Override
run()104     public void run() {
105         super.run();
106         if (mUiNodes == null || mUiNodes.size() < 1) {
107             return;
108         }
109 
110         // Commit the current pages first, to make sure the XML is in sync.
111         // Committing may change the XML structure.
112         if (mXmlCommit != null) {
113             mXmlCommit.commitPendingXmlChanges();
114         }
115 
116         StringBuilder allText = new StringBuilder();
117         ArrayList<UiElementNode> nodesToCut = mPerformCut ? new ArrayList<UiElementNode>() : null;
118 
119         for (UiElementNode uiNode : mUiNodes) {
120             try {
121                 Node xml_node = uiNode.getXmlNode();
122                 if (xml_node == null) {
123                     return;
124                 }
125 
126                 String data = getXmlTextFromEditor(xml_node);
127 
128                 // In the unlikely event that IStructuredDocument failed to extract the text
129                 // directly from the editor, try to fall back on a direct XML serialization
130                 // of the XML node. This uses the generic Node interface with no SSE tricks.
131                 if (data == null) {
132                     data = getXmlTextFromSerialization(xml_node);
133                 }
134 
135                 if (data != null) {
136                     allText.append(data);
137                     if (mPerformCut) {
138                         // only remove notes to cut if we actually got some XML text from them
139                         nodesToCut.add(uiNode);
140                     }
141                 }
142 
143             } catch (Exception e) {
144                 AdtPlugin.log(e, "CopyCutAction failed for UI node %1$s", //$NON-NLS-1$
145                         uiNode.getBreadcrumbTrailDescription(true));
146             }
147         } // for uiNode
148 
149         if (allText != null && allText.length() > 0) {
150             mClipboard.setContents(
151                     new Object[] { allText.toString() },
152                     new Transfer[] { TextTransfer.getInstance() });
153             if (mPerformCut) {
154                 for (UiElementNode uiNode : nodesToCut) {
155                     uiNode.deleteXmlNode();
156                 }
157             }
158         }
159     }
160 
161     /** Get the data directly from the editor. */
getXmlTextFromEditor(Node xml_node)162     private String getXmlTextFromEditor(Node xml_node) {
163         String data = null;
164         IStructuredModel model = mEditor.getModelForRead();
165         try {
166             IStructuredDocument sse_doc = mEditor.getStructuredDocument();
167             if (xml_node instanceof NodeContainer) {
168                 // The easy way to get the source of an SSE XML node.
169                 data = ((NodeContainer) xml_node).getSource();
170             } else  if (xml_node instanceof IndexedRegion && sse_doc != null) {
171                 // Try harder.
172                 IndexedRegion region = (IndexedRegion) xml_node;
173                 int start = region.getStartOffset();
174                 int end = region.getEndOffset();
175 
176                 if (end > start) {
177                     data = sse_doc.get(start, end - start);
178                 }
179             }
180         } catch (BadLocationException e) {
181             // the region offset was invalid. ignore.
182         } finally {
183             model.releaseFromRead();
184         }
185         return data;
186     }
187 
188     /**
189      * Direct XML serialization of the XML node.
190      * <p/>
191      * This uses the generic Node interface with no SSE tricks. It's however slower
192      * and doesn't respect formatting (since serialization is involved instead of reading
193      * the actual text buffer.)
194      */
getXmlTextFromSerialization(Node xml_node)195     private String getXmlTextFromSerialization(Node xml_node) throws IOException {
196         String data;
197         StringWriter sw = new StringWriter();
198         XMLSerializer serializer = new XMLSerializer(sw,
199                 new OutputFormat(Method.XML,
200                         OutputFormat.Defaults.Encoding /* utf-8 */,
201                         true /* indent */));
202         // Serialize will throw an IOException if it fails.
203         serializer.serialize((Element) xml_node);
204         data = sw.toString();
205         return data;
206     }
207 
208     /**
209      * Static helper class to wrap on node into a list for the constructors.
210      */
toList(UiElementNode selected)211     private static ArrayList<UiElementNode> toList(UiElementNode selected) {
212         ArrayList<UiElementNode> list = null;
213         if (selected != null) {
214             list = new ArrayList<UiElementNode>(1);
215             list.add(selected);
216         }
217         return list;
218     }
219 }
220 
221