Home | History | Annotate | Download | only in tree
      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      */
     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      */
     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
    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. */
    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      */
    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      */
    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