Home | History | Annotate | Download | only in gle2
      1 /*
      2  * Copyright (C) 2009 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.layout.gle2;
     18 
     19 import com.android.ide.common.api.INode;
     20 import com.android.ide.common.api.Margins;
     21 import com.android.ide.common.api.Point;
     22 import com.android.ide.common.layout.LayoutConstants;
     23 import com.android.ide.common.rendering.api.Capability;
     24 import com.android.ide.common.rendering.api.RenderSession;
     25 import com.android.ide.eclipse.adt.AdtPlugin;
     26 import com.android.ide.eclipse.adt.internal.editors.descriptors.DescriptorsUtils;
     27 import com.android.ide.eclipse.adt.internal.editors.descriptors.XmlnsAttributeDescriptor;
     28 import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditor;
     29 import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationComposite;
     30 import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewElementDescriptor;
     31 import com.android.ide.eclipse.adt.internal.editors.layout.gle2.IncludeFinder.Reference;
     32 import com.android.ide.eclipse.adt.internal.editors.layout.gre.NodeFactory;
     33 import com.android.ide.eclipse.adt.internal.editors.layout.gre.RulesEngine;
     34 import com.android.ide.eclipse.adt.internal.editors.layout.gre.ViewMetadataRepository;
     35 import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode;
     36 import com.android.ide.eclipse.adt.internal.editors.uimodel.UiDocumentNode;
     37 import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
     38 import com.android.resources.Density;
     39 import com.android.sdklib.SdkConstants;
     40 
     41 import org.eclipse.core.filesystem.EFS;
     42 import org.eclipse.core.filesystem.IFileStore;
     43 import org.eclipse.core.resources.IFile;
     44 import org.eclipse.core.resources.IWorkspaceRoot;
     45 import org.eclipse.core.resources.ResourcesPlugin;
     46 import org.eclipse.core.runtime.CoreException;
     47 import org.eclipse.core.runtime.IPath;
     48 import org.eclipse.core.runtime.QualifiedName;
     49 import org.eclipse.jdt.internal.ui.javaeditor.EditorUtility;
     50 import org.eclipse.jface.action.Action;
     51 import org.eclipse.jface.action.ActionContributionItem;
     52 import org.eclipse.jface.action.IAction;
     53 import org.eclipse.jface.action.IContributionItem;
     54 import org.eclipse.jface.action.IMenuManager;
     55 import org.eclipse.jface.action.IStatusLineManager;
     56 import org.eclipse.jface.action.MenuManager;
     57 import org.eclipse.jface.action.Separator;
     58 import org.eclipse.swt.SWT;
     59 import org.eclipse.swt.custom.StyledText;
     60 import org.eclipse.swt.dnd.DND;
     61 import org.eclipse.swt.dnd.DragSource;
     62 import org.eclipse.swt.dnd.DropTarget;
     63 import org.eclipse.swt.dnd.TextTransfer;
     64 import org.eclipse.swt.dnd.Transfer;
     65 import org.eclipse.swt.events.ControlAdapter;
     66 import org.eclipse.swt.events.ControlEvent;
     67 import org.eclipse.swt.events.KeyEvent;
     68 import org.eclipse.swt.events.MenuDetectEvent;
     69 import org.eclipse.swt.events.MenuDetectListener;
     70 import org.eclipse.swt.events.MouseEvent;
     71 import org.eclipse.swt.events.PaintEvent;
     72 import org.eclipse.swt.events.PaintListener;
     73 import org.eclipse.swt.graphics.Font;
     74 import org.eclipse.swt.graphics.GC;
     75 import org.eclipse.swt.graphics.Image;
     76 import org.eclipse.swt.graphics.ImageData;
     77 import org.eclipse.swt.graphics.Rectangle;
     78 import org.eclipse.swt.widgets.Canvas;
     79 import org.eclipse.swt.widgets.Composite;
     80 import org.eclipse.swt.widgets.Control;
     81 import org.eclipse.swt.widgets.Display;
     82 import org.eclipse.swt.widgets.Menu;
     83 import org.eclipse.ui.IActionBars;
     84 import org.eclipse.ui.IEditorPart;
     85 import org.eclipse.ui.IEditorSite;
     86 import org.eclipse.ui.IWorkbenchPage;
     87 import org.eclipse.ui.PartInitException;
     88 import org.eclipse.ui.actions.ActionFactory;
     89 import org.eclipse.ui.actions.ActionFactory.IWorkbenchAction;
     90 import org.eclipse.ui.actions.ContributionItemFactory;
     91 import org.eclipse.ui.ide.IDE;
     92 import org.eclipse.ui.internal.ide.IDEWorkbenchMessages;
     93 import org.eclipse.ui.texteditor.ITextEditor;
     94 import org.eclipse.ui.views.contentoutline.IContentOutlinePage;
     95 import org.w3c.dom.Node;
     96 
     97 import java.util.HashSet;
     98 import java.util.List;
     99 import java.util.Set;
    100 
    101 /**
    102  * Displays the image rendered by the {@link GraphicalEditorPart} and handles
    103  * the interaction with the widgets.
    104  * <p/>
    105  * {@link LayoutCanvas} implements the "Canvas" control. The editor part
    106  * actually uses the {@link LayoutCanvasViewer}, which is a JFace viewer wrapper
    107  * around this control.
    108  * <p/>
    109  * The LayoutCanvas contains the painting logic for the canvas. Selection,
    110  * clipboard, view management etc. is handled in separate helper classes.
    111  *
    112  * @since GLE2
    113  */
    114 @SuppressWarnings("restriction") // For WorkBench "Show In" support
    115 public class LayoutCanvas extends Canvas {
    116     private final static QualifiedName NAME_ZOOM =
    117         new QualifiedName(AdtPlugin.PLUGIN_ID, "zoom");//$NON-NLS-1$
    118 
    119     private static final boolean DEBUG = false;
    120 
    121     /* package */ static final String PREFIX_CANVAS_ACTION = "canvas_action_";
    122 
    123     /** The layout editor that uses this layout canvas. */
    124     private final LayoutEditor mLayoutEditor;
    125 
    126     /** The Rules Engine, associated with the current project. */
    127     private RulesEngine mRulesEngine;
    128 
    129     /** GC wrapper given to the IViewRule methods. The GC itself is only defined in the
    130      *  context of {@link #onPaint(PaintEvent)}; otherwise it is null. */
    131     private GCWrapper mGCWrapper;
    132 
    133     /** Default font used on the canvas. Do not dispose, it's a system font. */
    134     private Font mFont;
    135 
    136     /** Current hover view info. Null when no mouse hover. */
    137     private CanvasViewInfo mHoverViewInfo;
    138 
    139     /** When true, always display the outline of all views. */
    140     private boolean mShowOutline;
    141 
    142     /** When true, display the outline of all empty parent views. */
    143     private boolean mShowInvisible;
    144 
    145     /** Drop target associated with this composite. */
    146     private DropTarget mDropTarget;
    147 
    148     /** Factory that can create {@link INode} proxies. */
    149     private final NodeFactory mNodeFactory = new NodeFactory(this);
    150 
    151     /** Vertical scaling & scrollbar information. */
    152     private CanvasTransform mVScale;
    153 
    154     /** Horizontal scaling & scrollbar information. */
    155     private CanvasTransform mHScale;
    156 
    157     /** Drag source associated with this canvas. */
    158     private DragSource mDragSource;
    159 
    160     /**
    161      * The current Outline Page, to set its model.
    162      * It isn't possible to call OutlinePage2.dispose() in this.dispose().
    163      * this.dispose() is called from GraphicalEditorPart.dispose(),
    164      * when page's widget is already disposed.
    165      * Added the DisposeListener to OutlinePage2 in order to correctly dispose this page.
    166      **/
    167     private OutlinePage mOutlinePage;
    168 
    169     /** Delete action for the Edit or context menu. */
    170     private Action mDeleteAction;
    171 
    172     /** Select-All action for the Edit or context menu. */
    173     private Action mSelectAllAction;
    174 
    175     /** Paste action for the Edit or context menu. */
    176     private Action mPasteAction;
    177 
    178     /** Cut action for the Edit or context menu. */
    179     private Action mCutAction;
    180 
    181     /** Copy action for the Edit or context menu. */
    182     private Action mCopyAction;
    183 
    184     /** Root of the context menu. */
    185     private MenuManager mMenuManager;
    186 
    187     /** The view hierarchy associated with this canvas. */
    188     private final ViewHierarchy mViewHierarchy = new ViewHierarchy(this);
    189 
    190     /** The selection in the canvas. */
    191     private final SelectionManager mSelectionManager = new SelectionManager(this);
    192 
    193     /** The overlay which paints the optional outline. */
    194     private OutlineOverlay mOutlineOverlay;
    195 
    196     /** The overlay which paints outlines around empty children */
    197     private EmptyViewsOverlay mEmptyOverlay;
    198 
    199     /** The overlay which paints the mouse hover. */
    200     private HoverOverlay mHoverOverlay;
    201 
    202     /** The overlay which paints the selection. */
    203     private SelectionOverlay mSelectionOverlay;
    204 
    205     /** The overlay which paints the rendered layout image. */
    206     private ImageOverlay mImageOverlay;
    207 
    208     /** The overlay which paints masks hiding everything but included content. */
    209     private IncludeOverlay mIncludeOverlay;
    210 
    211     /**
    212      * Gesture Manager responsible for identifying mouse, keyboard and drag and
    213      * drop events.
    214      */
    215     private final GestureManager mGestureManager = new GestureManager(this);
    216 
    217     /**
    218      * When set, performs a zoom-to-fit when the next rendering image arrives.
    219      */
    220     private boolean mZoomFitNextImage;
    221 
    222     /**
    223      * Native clipboard support.
    224      */
    225     private ClipboardSupport mClipboardSupport;
    226 
    227     public LayoutCanvas(LayoutEditor layoutEditor,
    228             RulesEngine rulesEngine,
    229             Composite parent,
    230             int style) {
    231         super(parent, style | SWT.DOUBLE_BUFFERED | SWT.V_SCROLL | SWT.H_SCROLL);
    232         mLayoutEditor = layoutEditor;
    233         mRulesEngine = rulesEngine;
    234 
    235         mClipboardSupport = new ClipboardSupport(this, parent);
    236         mHScale = new CanvasTransform(this, getHorizontalBar());
    237         mVScale = new CanvasTransform(this, getVerticalBar());
    238 
    239         // Unit test suite passes a null here; TODO: Replace with mocking
    240         IFile file = layoutEditor != null ? layoutEditor.getInputFile() : null;
    241         if (file != null) {
    242             String zoom = AdtPlugin.getFileProperty(file, NAME_ZOOM);
    243             if (zoom != null) {
    244                 try {
    245                     double initialScale = Double.parseDouble(zoom);
    246                     if (initialScale > 0.1) {
    247                         mHScale.setScale(initialScale);
    248                         mVScale.setScale(initialScale);
    249                     }
    250                 } catch (NumberFormatException nfe) {
    251                     // Ignore - use zoom=100%
    252                 }
    253             } else {
    254                 mZoomFitNextImage = true;
    255             }
    256         }
    257 
    258         mGCWrapper = new GCWrapper(mHScale, mVScale);
    259 
    260         Display display = getDisplay();
    261         mFont = display.getSystemFont();
    262 
    263         // --- Set up graphic overlays
    264         // mOutlineOverlay and mEmptyOverlay are initialized lazily
    265         mHoverOverlay = new HoverOverlay(this, mHScale, mVScale);
    266         mHoverOverlay.create(display);
    267         mSelectionOverlay = new SelectionOverlay(this);
    268         mSelectionOverlay.create(display);
    269         mImageOverlay = new ImageOverlay(this, mHScale, mVScale);
    270         mIncludeOverlay = new IncludeOverlay(this);
    271         mImageOverlay.create(display);
    272 
    273         // --- Set up listeners
    274         addPaintListener(new PaintListener() {
    275             public void paintControl(PaintEvent e) {
    276                 onPaint(e);
    277             }
    278         });
    279 
    280         addControlListener(new ControlAdapter() {
    281             @Override
    282             public void controlResized(ControlEvent e) {
    283                 super.controlResized(e);
    284                 mHScale.setClientSize(getClientArea().width);
    285                 mVScale.setClientSize(getClientArea().height);
    286             }
    287         });
    288 
    289         // --- setup drag'n'drop ---
    290         // DND Reference: http://www.eclipse.org/articles/Article-SWT-DND/DND-in-SWT.html
    291 
    292         mDropTarget = createDropTarget(this);
    293         mDragSource = createDragSource(this);
    294         mGestureManager.registerListeners(mDragSource, mDropTarget);
    295 
    296         if (mLayoutEditor == null) {
    297             // TODO: In another CL we should use EasyMock/objgen to provide an editor.
    298             return; // Unit test
    299         }
    300 
    301         // --- setup context menu ---
    302         setupGlobalActionHandlers();
    303         createContextMenu();
    304 
    305         // --- setup outline ---
    306         // Get the outline associated with this editor, if any and of the right type.
    307         Object outline = layoutEditor.getAdapter(IContentOutlinePage.class);
    308         if (outline instanceof OutlinePage) {
    309             mOutlinePage = (OutlinePage) outline;
    310         }
    311     }
    312 
    313     public void handleKeyPressed(KeyEvent e) {
    314         // Set up backspace as an alias for the delete action within the canvas.
    315         // On most Macs there is no delete key - though there IS a key labeled
    316         // "Delete" and it sends a backspace key code! In short, for Macs we should
    317         // treat backspace as delete, and it's harmless (and probably useful) to
    318         // handle backspace for other platforms as well.
    319         if (e.keyCode == SWT.BS) {
    320             mDeleteAction.run();
    321         } else if (e.keyCode == SWT.ESC) {
    322             mSelectionManager.selectParent();
    323         } else {
    324             // Zooming actions
    325             char c = e.character;
    326             LayoutActionBar actionBar = mLayoutEditor.getGraphicalEditor()
    327                     .getLayoutActionBar();
    328             if (c == '1' && actionBar.isZoomingAllowed()) {
    329                 setScale(1, true);
    330             } else if (c == '0' && actionBar.isZoomingAllowed()) {
    331                 setFitScale(true);
    332             } else if (e.keyCode == '0' && (e.stateMask & SWT.MOD2) != 0
    333                     && actionBar.isZoomingAllowed()) {
    334                 setFitScale(false);
    335             } else if (c == '+' && actionBar.isZoomingAllowed()) {
    336                 actionBar.rescale(1);
    337             } else if (c == '-' && actionBar.isZoomingAllowed()) {
    338                 actionBar.rescale(-1);
    339             }
    340         }
    341     }
    342 
    343     @Override
    344     public void dispose() {
    345         super.dispose();
    346 
    347         mGestureManager.unregisterListeners(mDragSource, mDropTarget);
    348 
    349         if (mDropTarget != null) {
    350             mDropTarget.dispose();
    351             mDropTarget = null;
    352         }
    353 
    354         if (mRulesEngine != null) {
    355             mRulesEngine.dispose();
    356             mRulesEngine = null;
    357         }
    358 
    359         if (mDragSource != null) {
    360             mDragSource.dispose();
    361             mDragSource = null;
    362         }
    363 
    364         if (mClipboardSupport != null) {
    365             mClipboardSupport.dispose();
    366             mClipboardSupport = null;
    367         }
    368 
    369         if (mGCWrapper != null) {
    370             mGCWrapper.dispose();
    371             mGCWrapper = null;
    372         }
    373 
    374         if (mOutlineOverlay != null) {
    375             mOutlineOverlay.dispose();
    376             mOutlineOverlay = null;
    377         }
    378 
    379         if (mEmptyOverlay != null) {
    380             mEmptyOverlay.dispose();
    381             mEmptyOverlay = null;
    382         }
    383 
    384         if (mHoverOverlay != null) {
    385             mHoverOverlay.dispose();
    386             mHoverOverlay = null;
    387         }
    388 
    389         if (mSelectionOverlay != null) {
    390             mSelectionOverlay.dispose();
    391             mSelectionOverlay = null;
    392         }
    393 
    394         if (mImageOverlay != null) {
    395             mImageOverlay.dispose();
    396             mImageOverlay = null;
    397         }
    398 
    399         if (mIncludeOverlay != null) {
    400             mIncludeOverlay.dispose();
    401             mIncludeOverlay = null;
    402         }
    403 
    404         mViewHierarchy.dispose();
    405     }
    406 
    407     /** Returns the Rules Engine, associated with the current project. */
    408     /* package */ RulesEngine getRulesEngine() {
    409         return mRulesEngine;
    410     }
    411 
    412     /** Sets the Rules Engine, associated with the current project. */
    413     /* package */ void setRulesEngine(RulesEngine rulesEngine) {
    414         mRulesEngine = rulesEngine;
    415     }
    416 
    417     /**
    418      * Returns the factory to use to convert from {@link CanvasViewInfo} or from
    419      * {@link UiViewElementNode} to {@link INode} proxies.
    420      */
    421     /* package */ NodeFactory getNodeFactory() {
    422         return mNodeFactory;
    423     }
    424 
    425     /**
    426      * Returns the GCWrapper used to paint view rules.
    427      *
    428      * @return The GCWrapper used to paint view rules
    429      */
    430     /* package */ GCWrapper getGcWrapper() {
    431         return mGCWrapper;
    432     }
    433 
    434     /**
    435      * Returns the {@link LayoutEditor} associated with this canvas.
    436      */
    437     public LayoutEditor getLayoutEditor() {
    438         return mLayoutEditor;
    439     }
    440 
    441     /**
    442      * Returns the current {@link ImageOverlay} painting the rendered result
    443      *
    444      * @return the image overlay responsible for painting the rendered result, never null
    445      */
    446     ImageOverlay getImageOverlay() {
    447         return mImageOverlay;
    448     }
    449 
    450     /**
    451      * Returns the current {@link SelectionOverlay} painting the selection highlights
    452      *
    453      * @return the selection overlay responsible for painting the selection highlights,
    454      *         never null
    455      */
    456     SelectionOverlay getSelectionOverlay() {
    457         return mSelectionOverlay;
    458     }
    459 
    460     /**
    461      * Returns the {@link GestureManager} associated with this canvas.
    462      *
    463      * @return the {@link GestureManager} associated with this canvas, never null.
    464      */
    465     GestureManager getGestureManager() {
    466         return mGestureManager;
    467     }
    468 
    469     /**
    470      * Returns the current {@link HoverOverlay} painting the mouse hover.
    471      *
    472      * @return the hover overlay responsible for painting the mouse hover,
    473      *         never null
    474      */
    475     HoverOverlay getHoverOverlay() {
    476         return mHoverOverlay;
    477     }
    478 
    479     /**
    480      * Returns the horizontal {@link CanvasTransform} transform object, which can map
    481      * a layout point into a control point.
    482      *
    483      * @return A {@link CanvasTransform} for mapping between layout and control
    484      *         coordinates in the horizontal dimension.
    485      */
    486     /* package */ CanvasTransform getHorizontalTransform() {
    487         return mHScale;
    488     }
    489 
    490     /**
    491      * Returns the vertical {@link CanvasTransform} transform object, which can map a
    492      * layout point into a control point.
    493      *
    494      * @return A {@link CanvasTransform} for mapping between layout and control
    495      *         coordinates in the vertical dimension.
    496      */
    497     /* package */ CanvasTransform getVerticalTransform() {
    498         return mVScale;
    499     }
    500 
    501     /**
    502      * Returns the {@link OutlinePage} associated with this canvas
    503      *
    504      * @return the {@link OutlinePage} associated with this canvas
    505      */
    506     public OutlinePage getOutlinePage() {
    507         return mOutlinePage;
    508     }
    509 
    510     /**
    511      * Returns the {@link SelectionManager} associated with this canvas.
    512      *
    513      * @return The {@link SelectionManager} holding the selection for this
    514      *         canvas. Never null.
    515      */
    516     public SelectionManager getSelectionManager() {
    517         return mSelectionManager;
    518     }
    519 
    520     /**
    521      * Returns the {@link ViewHierarchy} object associated with this canvas,
    522      * holding the most recent rendered view of the scene, if valid.
    523      *
    524      * @return The {@link ViewHierarchy} object associated with this canvas.
    525      *         Never null.
    526      */
    527     public ViewHierarchy getViewHierarchy() {
    528         return mViewHierarchy;
    529     }
    530 
    531     /**
    532      * Returns the {@link ClipboardSupport} object associated with this canvas.
    533      *
    534      * @return The {@link ClipboardSupport} object for this canvas. Null only after dispose.
    535      */
    536     public ClipboardSupport getClipboardSupport() {
    537         return mClipboardSupport;
    538     }
    539 
    540     /** Returns the Select All action bound to this canvas */
    541     Action getSelectAllAction() {
    542         return mSelectAllAction;
    543     }
    544 
    545     /**
    546      * Sets the result of the layout rendering. The result object indicates if the layout
    547      * rendering succeeded. If it did, it contains a bitmap and the objects rectangles.
    548      *
    549      * Implementation detail: the bridge's computeLayout() method already returns a newly
    550      * allocated ILayourResult. That means we can keep this result and hold on to it
    551      * when it is valid.
    552      *
    553      * @param session The new scene, either valid or not.
    554      * @param explodedNodes The set of individual nodes the layout computer was asked to
    555      *            explode. Note that these are independent of the explode-all mode where
    556      *            all views are exploded; this is used only for the mode (
    557      *            {@link #showInvisibleViews(boolean)}) where individual invisible nodes
    558      *            are padded during certain interactions.
    559      */
    560     /* package */ void setSession(RenderSession session, Set<UiElementNode> explodedNodes,
    561             boolean layoutlib5) {
    562         // disable any hover
    563         clearHover();
    564 
    565         mViewHierarchy.setSession(session, explodedNodes, layoutlib5);
    566         if (mViewHierarchy.isValid() && session != null) {
    567             Image image = mImageOverlay.setImage(session.getImage(), session.isAlphaChannelImage());
    568 
    569             mOutlinePage.setModel(mViewHierarchy.getRoot());
    570 
    571             if (image != null) {
    572                 mHScale.setSize(image.getImageData().width, getClientArea().width);
    573                 mVScale.setSize(image.getImageData().height, getClientArea().height);
    574                 if (mZoomFitNextImage) {
    575                     mZoomFitNextImage = false;
    576                     // Must be run asynchronously because getClientArea() returns 0 bounds
    577                     // when the editor is being initialized
    578                     getDisplay().asyncExec(new Runnable() {
    579                         public void run() {
    580                             setFitScale(true);
    581                         }
    582                     });
    583                 }
    584             }
    585         }
    586 
    587         redraw();
    588     }
    589 
    590     /* package */ void setShowOutline(boolean newState) {
    591         mShowOutline = newState;
    592         redraw();
    593     }
    594 
    595     public double getScale() {
    596         return mHScale.getScale();
    597     }
    598 
    599     /* package */ void setScale(double scale, boolean redraw) {
    600         if (scale <= 0.0) {
    601             scale = 1.0;
    602         }
    603 
    604         if (scale == getScale()) {
    605             return;
    606         }
    607 
    608         mHScale.setScale(scale);
    609         mVScale.setScale(scale);
    610         if (redraw) {
    611             redraw();
    612         }
    613 
    614         // Clear the zoom setting if it is almost identical to 1.0
    615         String zoomValue = (Math.abs(scale - 1.0) < 0.0001) ? null : Double.toString(scale);
    616         AdtPlugin.setFileProperty(mLayoutEditor.getInputFile(), NAME_ZOOM, zoomValue);
    617     }
    618 
    619     /**
    620      * Scales the canvas to best fit
    621      *
    622      * @param onlyZoomOut if true, then the zooming factor will never be larger than 1,
    623      *            which means that this function will zoom out if necessary to show the
    624      *            rendered image, but it will never zoom in.
    625      */
    626     void setFitScale(boolean onlyZoomOut) {
    627         Image image = getImageOverlay().getImage();
    628         if (image != null) {
    629             Rectangle canvasSize = getClientArea();
    630             int canvasWidth = canvasSize.width;
    631             int canvasHeight = canvasSize.height;
    632 
    633             ImageData imageData = image.getImageData();
    634             int sceneWidth = imageData.width;
    635             int sceneHeight = imageData.height;
    636             if (sceneWidth == 0.0 || sceneHeight == 0.0) {
    637                 return;
    638             }
    639 
    640             // Reduce the margins if necessary
    641             int hDelta = canvasWidth - sceneWidth;
    642             int hMargin = 0;
    643             if (hDelta > 2 * CanvasTransform.DEFAULT_MARGIN) {
    644                 hMargin = CanvasTransform.DEFAULT_MARGIN;
    645             } else if (hDelta > 0) {
    646                 hMargin = hDelta / 2;
    647             }
    648 
    649             int vDelta = canvasHeight - sceneHeight;
    650             int vMargin = 0;
    651             if (vDelta > 2 * CanvasTransform.DEFAULT_MARGIN) {
    652                 vMargin = CanvasTransform.DEFAULT_MARGIN;
    653             } else if (vDelta > 0) {
    654                 vMargin = vDelta / 2;
    655             }
    656 
    657             double hScale = (canvasWidth - 2 * hMargin) / (double) sceneWidth;
    658             double vScale = (canvasHeight - 2 * vMargin) / (double) sceneHeight;
    659 
    660             double scale = Math.min(hScale, vScale);
    661 
    662             if (onlyZoomOut) {
    663                 scale = Math.min(1.0, scale);
    664             }
    665 
    666             setScale(scale, true);
    667         }
    668     }
    669 
    670     /**
    671      * Transforms a point, expressed in layout coordinates, into "client" coordinates
    672      * relative to the control (and not relative to the display).
    673      *
    674      * @param canvasX X in the canvas coordinates
    675      * @param canvasY Y in the canvas coordinates
    676      * @return A new {@link Point} in control client coordinates (not display coordinates)
    677      */
    678     /* package */ Point layoutToControlPoint(int canvasX, int canvasY) {
    679         int x = mHScale.translate(canvasX);
    680         int y = mVScale.translate(canvasY);
    681         return new Point(x, y);
    682     }
    683 
    684     /**
    685      * Returns the action for the context menu corresponding to the given action id.
    686      * <p/>
    687      * For global actions such as copy or paste, the action id must be composed of
    688      * the {@link #PREFIX_CANVAS_ACTION} followed by one of {@link ActionFactory}'s
    689      * action ids.
    690      * <p/>
    691      * Returns null if there's no action for the given id.
    692      */
    693     /* package */ IAction getAction(String actionId) {
    694         String prefix = PREFIX_CANVAS_ACTION;
    695         if (mMenuManager == null ||
    696                 actionId == null ||
    697                 !actionId.startsWith(prefix)) {
    698             return null;
    699         }
    700 
    701         actionId = actionId.substring(prefix.length());
    702 
    703         for (IContributionItem contrib : mMenuManager.getItems()) {
    704             if (contrib instanceof ActionContributionItem &&
    705                     actionId.equals(contrib.getId())) {
    706                 return ((ActionContributionItem) contrib).getAction();
    707             }
    708         }
    709 
    710         return null;
    711     }
    712 
    713     //---------------
    714 
    715     /**
    716      * Paints the canvas in response to paint events.
    717      */
    718     private void onPaint(PaintEvent e) {
    719         GC gc = e.gc;
    720         gc.setFont(mFont);
    721         mGCWrapper.setGC(gc);
    722         try {
    723             if (!mImageOverlay.isHiding()) {
    724                 mImageOverlay.paint(gc);
    725             }
    726 
    727             if (mShowOutline) {
    728                 if (mOutlineOverlay == null) {
    729                     mOutlineOverlay = new OutlineOverlay(mViewHierarchy, mHScale, mVScale);
    730                     mOutlineOverlay.create(getDisplay());
    731                 }
    732                 if (!mOutlineOverlay.isHiding()) {
    733                     mOutlineOverlay.paint(gc);
    734                 }
    735             }
    736 
    737             if (mShowInvisible) {
    738                 if (mEmptyOverlay == null) {
    739                     mEmptyOverlay = new EmptyViewsOverlay(mViewHierarchy, mHScale, mVScale);
    740                     mEmptyOverlay.create(getDisplay());
    741                 }
    742                 if (!mEmptyOverlay.isHiding()) {
    743                     mEmptyOverlay.paint(gc);
    744                 }
    745             }
    746 
    747             if (!mHoverOverlay.isHiding()) {
    748                 mHoverOverlay.paint(gc);
    749             }
    750             if (!mIncludeOverlay.isHiding()) {
    751                 mIncludeOverlay.paint(gc);
    752             }
    753 
    754             if (!mSelectionOverlay.isHiding()) {
    755                 mSelectionOverlay.paint(mSelectionManager, mGCWrapper, gc, mRulesEngine);
    756             }
    757             mGestureManager.paint(gc);
    758 
    759         } finally {
    760             mGCWrapper.setGC(null);
    761         }
    762     }
    763 
    764     /**
    765      * Shows or hides invisible parent views, which are views which have empty bounds and
    766      * no children. The nodes which will be shown are provided by
    767      * {@link #getNodesToExplode()}.
    768      *
    769      * @param show When true, any invisible parent nodes are padded and highlighted
    770      *            ("exploded"), and when false any formerly exploded nodes are hidden.
    771      */
    772     /* package */ void showInvisibleViews(boolean show) {
    773         if (mShowInvisible == show) {
    774             return;
    775         }
    776         mShowInvisible = show;
    777 
    778         // Optimization: Avoid doing work when we don't have invisible parents (on show)
    779         // or formerly exploded nodes (on hide).
    780         if (show && !mViewHierarchy.hasInvisibleParents()) {
    781             return;
    782         } else if (!show && !mViewHierarchy.hasExplodedParents()) {
    783             return;
    784         }
    785 
    786         mLayoutEditor.recomputeLayout();
    787     }
    788 
    789     /**
    790      * Returns a set of nodes that should be exploded (forced non-zero padding during render),
    791      * or null if no nodes should be exploded. (Note that this is independent of the
    792      * explode-all mode, where all nodes are padded -- that facility does not use this
    793      * mechanism, which is only intended to be used to expose invisible parent nodes.
    794      *
    795      * @return The set of invisible parents, or null if no views should be expanded.
    796      */
    797     public Set<UiElementNode> getNodesToExplode() {
    798         if (mShowInvisible) {
    799             return mViewHierarchy.getInvisibleNodes();
    800         }
    801 
    802         // IF we have selection, and IF we have invisible nodes in the view,
    803         // see if any of the selected items are among the invisible nodes, and if so
    804         // add them to a lazily constructed set which we pass back for rendering.
    805         Set<UiElementNode> result = null;
    806         List<SelectionItem> selections = mSelectionManager.getSelections();
    807         if (selections.size() > 0) {
    808             List<CanvasViewInfo> invisibleParents = mViewHierarchy.getInvisibleViews();
    809             if (invisibleParents.size() > 0) {
    810                 for (SelectionItem item : selections) {
    811                     CanvasViewInfo viewInfo = item.getViewInfo();
    812                     // O(n^2) here, but both the selection size and especially the
    813                     // invisibleParents size are expected to be small
    814                     if (invisibleParents.contains(viewInfo)) {
    815                         UiViewElementNode node = viewInfo.getUiViewNode();
    816                         if (node != null) {
    817                             if (result == null) {
    818                                 result = new HashSet<UiElementNode>();
    819                             }
    820                             result.add(node);
    821                         }
    822                     }
    823                 }
    824             }
    825         }
    826 
    827         return result;
    828     }
    829 
    830     /**
    831      * Clears the hover.
    832      */
    833     /* package */ void clearHover() {
    834         mHoverOverlay.clearHover();
    835     }
    836 
    837     /**
    838      * Hover on top of a known child.
    839      */
    840     /* package */ void hover(MouseEvent e) {
    841         // Check if a button is pressed; no hovers during drags
    842         if ((e.stateMask & SWT.BUTTON_MASK) != 0) {
    843             clearHover();
    844             return;
    845         }
    846 
    847         LayoutPoint p = ControlPoint.create(this, e).toLayout();
    848         CanvasViewInfo vi = mViewHierarchy.findViewInfoAt(p);
    849 
    850         // We don't hover on the root since it's not a widget per see and it is always there.
    851         // We also skip spacers...
    852         if (vi != null && (vi.isRoot() || vi.isHidden())) {
    853             vi = null;
    854         }
    855 
    856         boolean needsUpdate = vi != mHoverViewInfo;
    857         mHoverViewInfo = vi;
    858 
    859         if (vi == null) {
    860             clearHover();
    861         } else {
    862             Rectangle r = vi.getSelectionRect();
    863             mHoverOverlay.setHover(r.x, r.y, r.width, r.height);
    864         }
    865 
    866         if (needsUpdate) {
    867             redraw();
    868         }
    869     }
    870 
    871     /**
    872      * Shows the given {@link CanvasViewInfo}, which can mean exposing its XML or if it's
    873      * an included element, its corresponding file.
    874      *
    875      * @param vi the {@link CanvasViewInfo} to be shown
    876      */
    877     public void show(CanvasViewInfo vi) {
    878         String url = vi.getIncludeUrl();
    879         if (url != null) {
    880             showInclude(url);
    881         } else {
    882             showXml(vi);
    883         }
    884     }
    885 
    886     /**
    887      * Shows the layout file referenced by the given url in the same project.
    888      *
    889      * @param url The layout attribute url of the form @layout/foo
    890      */
    891     private void showInclude(String url) {
    892         GraphicalEditorPart graphicalEditor = mLayoutEditor.getGraphicalEditor();
    893         IPath filePath = graphicalEditor.findResourceFile(url);
    894         if (filePath == null) {
    895             // Should not be possible - if the URL had been bad, then we wouldn't
    896             // have been able to render the scene and you wouldn't have been able
    897             // to click on it
    898             return;
    899         }
    900 
    901         // Save the including file, if necessary: without it, the "Show Included In"
    902         // facility which is invoked automatically will not work properly if the <include>
    903         // tag is not in the saved version of the file, since the outer file is read from
    904         // disk rather than from memory.
    905         IEditorSite editorSite = graphicalEditor.getEditorSite();
    906         IWorkbenchPage page = editorSite.getPage();
    907         page.saveEditor(mLayoutEditor, false);
    908 
    909         IWorkspaceRoot workspace = ResourcesPlugin.getWorkspace().getRoot();
    910         IFile xmlFile = null;
    911         IPath workspacePath = workspace.getLocation();
    912         if (workspacePath.isPrefixOf(filePath)) {
    913             IPath relativePath = filePath.makeRelativeTo(workspacePath);
    914             xmlFile = (IFile) workspace.findMember(relativePath);
    915         } else if (filePath.isAbsolute()) {
    916             xmlFile = workspace.getFileForLocation(filePath);
    917         }
    918         if (xmlFile != null) {
    919             IFile leavingFile = graphicalEditor.getEditedFile();
    920             Reference next = Reference.create(graphicalEditor.getEditedFile());
    921 
    922             try {
    923                 IEditorPart openAlready = EditorUtility.isOpenInEditor(xmlFile);
    924 
    925                 // Show the included file as included within this click source?
    926                 if (openAlready != null) {
    927                     if (openAlready instanceof LayoutEditor) {
    928                         LayoutEditor editor = (LayoutEditor)openAlready;
    929                         GraphicalEditorPart gEditor = editor.getGraphicalEditor();
    930                         if (gEditor.renderingSupports(Capability.EMBEDDED_LAYOUT)) {
    931                             gEditor.showIn(next);
    932                         }
    933                     }
    934                 } else {
    935                     try {
    936                         // Set initial state of a new file
    937                         // TODO: Only set rendering target portion of the state
    938                         QualifiedName qname = ConfigurationComposite.NAME_CONFIG_STATE;
    939                         String state = AdtPlugin.getFileProperty(leavingFile, qname);
    940                         xmlFile.setSessionProperty(GraphicalEditorPart.NAME_INITIAL_STATE,
    941                                 state);
    942                     } catch (CoreException e) {
    943                         // pass
    944                     }
    945 
    946                     if (graphicalEditor.renderingSupports(Capability.EMBEDDED_LAYOUT)) {
    947                         try {
    948                             xmlFile.setSessionProperty(GraphicalEditorPart.NAME_INCLUDE, next);
    949                         } catch (CoreException e) {
    950                             // pass - worst that can happen is that we don't
    951                             //start with inclusion
    952                         }
    953                     }
    954                 }
    955 
    956                 EditorUtility.openInEditor(xmlFile, true);
    957                 return;
    958             } catch (PartInitException ex) {
    959                 AdtPlugin.log(ex, "Can't open %$1s", url); //$NON-NLS-1$
    960             }
    961         } else {
    962             // It's not a path in the workspace; look externally
    963             // (this is probably an @android: path)
    964             if (filePath.isAbsolute()) {
    965                 IFileStore fileStore = EFS.getLocalFileSystem().getStore(filePath);
    966                 // fileStore = fileStore.getChild(names[i]);
    967                 if (!fileStore.fetchInfo().isDirectory() && fileStore.fetchInfo().exists()) {
    968                     try {
    969                         IDE.openEditorOnFileStore(page, fileStore);
    970                         return;
    971                     } catch (PartInitException ex) {
    972                         AdtPlugin.log(ex, "Can't open %$1s", url); //$NON-NLS-1$
    973                     }
    974                 }
    975             }
    976         }
    977 
    978         // Failed: display message to the user
    979         String message = String.format("Could not find resource %1$s", url);
    980         IStatusLineManager status = editorSite.getActionBars().getStatusLineManager();
    981         status.setErrorMessage(message);
    982         getDisplay().beep();
    983     }
    984 
    985     /**
    986      * Returns the layout resource name of this layout
    987      *
    988      * @return the layout resource name of this layout
    989      */
    990     public String getLayoutResourceName() {
    991         GraphicalEditorPart graphicalEditor = mLayoutEditor.getGraphicalEditor();
    992         return graphicalEditor.getLayoutResourceName();
    993     }
    994 
    995     /**
    996      * Returns the layout resource url of the current layout
    997      *
    998      * @return
    999      */
   1000     /*
   1001     public String getMe() {
   1002         GraphicalEditorPart graphicalEditor = mLayoutEditor.getGraphicalEditor();
   1003         IFile editedFile = graphicalEditor.getEditedFile();
   1004         return editedFile.getProjectRelativePath().toOSString();
   1005     }
   1006      */
   1007 
   1008     /**
   1009      * Show the XML element corresponding to the given {@link CanvasViewInfo} (unless it's
   1010      * a root).
   1011      *
   1012      * @param vi The clicked {@link CanvasViewInfo} whose underlying XML element we want
   1013      *            to view
   1014      */
   1015     private void showXml(CanvasViewInfo vi) {
   1016         // Warp to the text editor and show the corresponding XML for the
   1017         // double-clicked widget
   1018         if (vi.isRoot()) {
   1019             return;
   1020         }
   1021 
   1022         Node xmlNode = vi.getXmlNode();
   1023         if (xmlNode != null) {
   1024             boolean found = mLayoutEditor.show(xmlNode);
   1025             if (!found) {
   1026                 getDisplay().beep();
   1027             }
   1028         }
   1029     }
   1030 
   1031     //---------------
   1032 
   1033     /**
   1034      * Helper to create the drag source for the given control.
   1035      * <p/>
   1036      * This is static with package-access so that {@link OutlinePage} can also
   1037      * create an exact copy of the source with the same attributes.
   1038      */
   1039     /* package */static DragSource createDragSource(Control control) {
   1040         DragSource source = new DragSource(control, DND.DROP_COPY | DND.DROP_MOVE);
   1041         source.setTransfer(new Transfer[] {
   1042                 TextTransfer.getInstance(),
   1043                 SimpleXmlTransfer.getInstance()
   1044         });
   1045         return source;
   1046     }
   1047 
   1048     /**
   1049      * Helper to create the drop target for the given control.
   1050      */
   1051     private static DropTarget createDropTarget(Control control) {
   1052         DropTarget dropTarget = new DropTarget(
   1053                 control, DND.DROP_COPY | DND.DROP_MOVE | DND.DROP_DEFAULT);
   1054         dropTarget.setTransfer(new Transfer[] {
   1055             SimpleXmlTransfer.getInstance()
   1056         });
   1057         return dropTarget;
   1058     }
   1059 
   1060     //---------------
   1061 
   1062     /**
   1063      * Invoked by the constructor to add our cut/copy/paste/delete/select-all
   1064      * handlers in the global action handlers of this editor's site.
   1065      * <p/>
   1066      * This will enable the menu items under the global Edit menu and make them
   1067      * invoke our actions as needed. As a benefit, the corresponding shortcut
   1068      * accelerators will do what one would expect.
   1069      */
   1070     private void setupGlobalActionHandlers() {
   1071         mCutAction = new Action() {
   1072             @Override
   1073             public void run() {
   1074                 mClipboardSupport.cutSelectionToClipboard(mSelectionManager.getSnapshot());
   1075                 updateMenuActionState();
   1076             }
   1077         };
   1078 
   1079         copyActionAttributes(mCutAction, ActionFactory.CUT);
   1080 
   1081         mCopyAction = new Action() {
   1082             @Override
   1083             public void run() {
   1084                 mClipboardSupport.copySelectionToClipboard(mSelectionManager.getSnapshot());
   1085                 updateMenuActionState();
   1086             }
   1087         };
   1088 
   1089         copyActionAttributes(mCopyAction, ActionFactory.COPY);
   1090 
   1091         mPasteAction = new Action() {
   1092             @Override
   1093             public void run() {
   1094                 mClipboardSupport.pasteSelection(mSelectionManager.getSnapshot());
   1095                 updateMenuActionState();
   1096             }
   1097         };
   1098 
   1099         copyActionAttributes(mPasteAction, ActionFactory.PASTE);
   1100 
   1101         mDeleteAction = new Action() {
   1102             @Override
   1103             public void run() {
   1104                 mClipboardSupport.deleteSelection(
   1105                         getDeleteLabel(),
   1106                         mSelectionManager.getSnapshot());
   1107             }
   1108         };
   1109 
   1110         copyActionAttributes(mDeleteAction, ActionFactory.DELETE);
   1111 
   1112         mSelectAllAction = new Action() {
   1113             @Override
   1114             public void run() {
   1115                 GraphicalEditorPart graphicalEditor = getLayoutEditor().getGraphicalEditor();
   1116                 StyledText errorLabel = graphicalEditor.getErrorLabel();
   1117                 if (errorLabel.isFocusControl()) {
   1118                     errorLabel.selectAll();
   1119                     return;
   1120                 }
   1121 
   1122                 mSelectionManager.selectAll();
   1123             }
   1124         };
   1125 
   1126         copyActionAttributes(mSelectAllAction, ActionFactory.SELECT_ALL);
   1127     }
   1128 
   1129     /* package */ String getCutLabel() {
   1130         return mCutAction.getText();
   1131     }
   1132 
   1133     /* package */ String getDeleteLabel() {
   1134         // verb "Delete" from the DELETE action's title
   1135         return mDeleteAction.getText();
   1136     }
   1137 
   1138     /**
   1139      * Updates menu actions that depends on the selection.
   1140      */
   1141     void updateMenuActionState() {
   1142         List<SelectionItem> selections = getSelectionManager().getSelections();
   1143         boolean hasSelection = !selections.isEmpty();
   1144         if (hasSelection && selections.size() == 1 && selections.get(0).isRoot()) {
   1145             hasSelection = false;
   1146         }
   1147 
   1148         StyledText errorLabel = mLayoutEditor.getGraphicalEditor().getErrorLabel();
   1149         mCutAction.setEnabled(hasSelection);
   1150         mCopyAction.setEnabled(hasSelection || errorLabel.getSelectionCount() > 0);
   1151         mDeleteAction.setEnabled(hasSelection);
   1152         // Select All should *always* be selectable, regardless of whether anything
   1153         // is currently selected.
   1154         mSelectAllAction.setEnabled(true);
   1155 
   1156         // The paste operation is only available if we can paste our custom type.
   1157         // We do not currently support pasting random text (e.g. XML). Maybe later.
   1158         boolean hasSxt = mClipboardSupport.hasSxtOnClipboard();
   1159         mPasteAction.setEnabled(hasSxt);
   1160     }
   1161 
   1162     /**
   1163      * Update the actions when this editor is activated
   1164      *
   1165      * @param bars the action bar for this canvas
   1166      */
   1167     public void updateGlobalActions(IActionBars bars) {
   1168         updateMenuActionState();
   1169 
   1170         bars.setGlobalActionHandler(ActionFactory.CUT.getId(), mCutAction);
   1171         bars.setGlobalActionHandler(ActionFactory.COPY.getId(), mCopyAction);
   1172         bars.setGlobalActionHandler(ActionFactory.PASTE.getId(), mPasteAction);
   1173         bars.setGlobalActionHandler(ActionFactory.DELETE.getId(), mDeleteAction);
   1174         bars.setGlobalActionHandler(ActionFactory.SELECT_ALL.getId(), mSelectAllAction);
   1175 
   1176         ITextEditor editor = mLayoutEditor.getStructuredTextEditor();
   1177         IAction undoAction = editor.getAction(ActionFactory.UNDO.getId());
   1178         bars.setGlobalActionHandler(ActionFactory.UNDO.getId(), undoAction);
   1179         IAction redoAction = editor.getAction(ActionFactory.REDO.getId());
   1180         bars.setGlobalActionHandler(ActionFactory.REDO.getId(), redoAction);
   1181 
   1182         bars.updateActionBars();
   1183     }
   1184 
   1185     /**
   1186      * Helper for {@link #setupGlobalActionHandlers()}.
   1187      * Copies the action attributes form the given {@link ActionFactory}'s action to
   1188      * our action.
   1189      * <p/>
   1190      * {@link ActionFactory} provides access to the standard global actions in Eclipse.
   1191      * <p/>
   1192      * This allows us to grab the standard labels and icons for the
   1193      * global actions such as copy, cut, paste, delete and select-all.
   1194      */
   1195     private void copyActionAttributes(Action action, ActionFactory factory) {
   1196         IWorkbenchAction wa = factory.create(mLayoutEditor.getEditorSite().getWorkbenchWindow());
   1197         action.setId(wa.getId());
   1198         action.setText(wa.getText());
   1199         action.setEnabled(wa.isEnabled());
   1200         action.setDescription(wa.getDescription());
   1201         action.setToolTipText(wa.getToolTipText());
   1202         action.setAccelerator(wa.getAccelerator());
   1203         action.setActionDefinitionId(wa.getActionDefinitionId());
   1204         action.setImageDescriptor(wa.getImageDescriptor());
   1205         action.setHoverImageDescriptor(wa.getHoverImageDescriptor());
   1206         action.setDisabledImageDescriptor(wa.getDisabledImageDescriptor());
   1207         action.setHelpListener(wa.getHelpListener());
   1208     }
   1209 
   1210     /**
   1211      * Creates the context menu for the canvas. This is called once from the canvas' constructor.
   1212      * <p/>
   1213      * The menu has a static part with actions that are always available such as
   1214      * copy, cut, paste and show in > explorer. This is created by
   1215      * {@link #setupStaticMenuActions(IMenuManager)}.
   1216      * <p/>
   1217      * There's also a dynamic part that is populated by the rules of the
   1218      * selected elements, created by {@link DynamicContextMenu}.
   1219      */
   1220     private void createContextMenu() {
   1221 
   1222         // This manager is the root of the context menu.
   1223         mMenuManager = new MenuManager() {
   1224             @Override
   1225             public boolean isDynamic() {
   1226                 return true;
   1227             }
   1228         };
   1229 
   1230         // Fill the menu manager with the static & dynamic actions
   1231         setupStaticMenuActions(mMenuManager);
   1232         new DynamicContextMenu(mLayoutEditor, this, mMenuManager);
   1233         Menu menu = mMenuManager.createContextMenu(this);
   1234         setMenu(menu);
   1235 
   1236         // Add listener to detect when the menu is about to be posted, such that
   1237         // we can sync the selection. Without this, you can right click on something
   1238         // in the canvas which is NOT selected, and the context menu will show items related
   1239         // to the selection, NOT the item you clicked on!!
   1240         addMenuDetectListener(new MenuDetectListener() {
   1241             public void menuDetected(MenuDetectEvent e) {
   1242                 mSelectionManager.menuClick(e);
   1243             }
   1244         });
   1245     }
   1246 
   1247     /**
   1248      * Invoked by {@link #createContextMenu()} to create our *static* context menu once.
   1249      * <p/>
   1250      * The content of the menu itself does not change. However the state of the
   1251      * various items is controlled by their associated actions.
   1252      * <p/>
   1253      * For cut/copy/paste/delete/select-all, we explicitly reuse the actions
   1254      * created by {@link #setupGlobalActionHandlers()}, so this method must be
   1255      * invoked after that one.
   1256      */
   1257     private void setupStaticMenuActions(IMenuManager manager) {
   1258         manager.removeAll();
   1259 
   1260         manager.add(new SelectionManager.SelectionMenu(mLayoutEditor.getGraphicalEditor()));
   1261         manager.add(new Separator());
   1262         manager.add(mCutAction);
   1263         manager.add(mCopyAction);
   1264         manager.add(mPasteAction);
   1265         manager.add(new Separator());
   1266         manager.add(mDeleteAction);
   1267         manager.add(new Separator());
   1268         manager.add(new PlayAnimationMenu(this));
   1269         manager.add(new Separator());
   1270 
   1271         // Group "Show Included In" and "Show In" together
   1272         manager.add(new ShowWithinMenu(mLayoutEditor));
   1273 
   1274         // Create a "Show In" sub-menu and automatically populate it using standard
   1275         // actions contributed by the workbench.
   1276         String showInLabel = IDEWorkbenchMessages.Workbench_showIn;
   1277         MenuManager showInSubMenu = new MenuManager(showInLabel);
   1278         showInSubMenu.add(
   1279                 ContributionItemFactory.VIEWS_SHOW_IN.create(
   1280                         mLayoutEditor.getSite().getWorkbenchWindow()));
   1281         manager.add(showInSubMenu);
   1282     }
   1283 
   1284     /**
   1285      * Deletes the selection. Equivalent to pressing the Delete key.
   1286      */
   1287     /* package */ void delete() {
   1288         mDeleteAction.run();
   1289     }
   1290 
   1291     /**
   1292      * Add new root in an existing empty XML layout.
   1293      * <p/>
   1294      * In case of error (unknown FQCN, document not empty), silently do nothing.
   1295      * In case of success, the new element will have some default attributes set
   1296      * (xmlns:android, layout_width and height). The edit is wrapped in a proper
   1297      * undo.
   1298      * <p/>
   1299      * This is invoked by
   1300      * {@link MoveGesture#drop(org.eclipse.swt.dnd.DropTargetEvent)}.
   1301      *
   1302      * @param rootFqcn A non-null non-empty FQCN that must match an existing
   1303      *            {@link ViewElementDescriptor} to add as root to the current
   1304      *            empty XML document.
   1305      */
   1306     /* package */ void createDocumentRoot(String rootFqcn) {
   1307 
   1308         // Need a valid empty document to create the new root
   1309         final UiDocumentNode uiDoc = mLayoutEditor.getUiRootNode();
   1310         if (uiDoc == null || uiDoc.getUiChildren().size() > 0) {
   1311             debugPrintf("Failed to create document root for %1$s: document is not empty", rootFqcn);
   1312             return;
   1313         }
   1314 
   1315         // Find the view descriptor matching our FQCN
   1316         final ViewElementDescriptor viewDesc = mLayoutEditor.getFqcnViewDescriptor(rootFqcn);
   1317         if (viewDesc == null) {
   1318             // TODO this could happen if dropping a custom view not known in this project
   1319             debugPrintf("Failed to add document root, unknown FQCN %1$s", rootFqcn);
   1320             return;
   1321         }
   1322 
   1323         // Get the last segment of the FQCN for the undo title
   1324         String title = rootFqcn;
   1325         int pos = title.lastIndexOf('.');
   1326         if (pos > 0 && pos < title.length() - 1) {
   1327             title = title.substring(pos + 1);
   1328         }
   1329         title = String.format("Create root %1$s in document", title);
   1330 
   1331         mLayoutEditor.wrapUndoEditXmlModel(title, new Runnable() {
   1332             public void run() {
   1333                 UiElementNode uiNew = uiDoc.appendNewUiChild(viewDesc);
   1334 
   1335                 // A root node requires the Android XMLNS
   1336                 uiNew.setAttributeValue(
   1337                         LayoutConstants.ANDROID_NS_NAME,
   1338                         XmlnsAttributeDescriptor.XMLNS_URI,
   1339                         SdkConstants.NS_RESOURCES,
   1340                         true /*override*/);
   1341 
   1342                 // Adjust the attributes
   1343                 DescriptorsUtils.setDefaultLayoutAttributes(uiNew, false /*updateLayout*/);
   1344 
   1345                 uiNew.createXmlNode();
   1346             }
   1347         });
   1348     }
   1349 
   1350     /**
   1351      * Returns the insets associated with views of the given fully qualified name, for the
   1352      * current theme and screen type.
   1353      *
   1354      * @param fqcn the fully qualified name to the widget type
   1355      * @return the insets, or null if unknown
   1356      */
   1357     public Margins getInsets(String fqcn) {
   1358         if (ViewMetadataRepository.INSETS_SUPPORTED) {
   1359             ConfigurationComposite configComposite =
   1360                     mLayoutEditor.getGraphicalEditor().getConfigurationComposite();
   1361             String theme = configComposite.getTheme();
   1362             Density density = configComposite.getDensity();
   1363             return ViewMetadataRepository.getInsets(fqcn, density, theme);
   1364         } else {
   1365             return null;
   1366         }
   1367     }
   1368 
   1369     private void debugPrintf(String message, Object... params) {
   1370         if (DEBUG) {
   1371             AdtPlugin.printToConsole("Canvas", String.format(message, params));
   1372         }
   1373     }
   1374 }
   1375