diff options
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java')
-rw-r--r-- | eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java | 1720 |
1 files changed, 0 insertions, 1720 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java deleted file mode 100644 index 814b82cec..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java +++ /dev/null @@ -1,1720 +0,0 @@ -/* - * Copyright (C) 2009 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.layout.gle2; - -import com.android.SdkConstants; -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.ide.common.api.IDragElement.IDragAttribute; -import com.android.ide.common.api.INode; -import com.android.ide.common.api.Margins; -import com.android.ide.common.api.Point; -import com.android.ide.common.rendering.api.Capability; -import com.android.ide.common.rendering.api.RenderSession; -import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.internal.editors.descriptors.DescriptorsUtils; -import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate; -import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationChooser; -import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationDescription; -import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewElementDescriptor; -import com.android.ide.eclipse.adt.internal.editors.layout.gle2.IncludeFinder.Reference; -import com.android.ide.eclipse.adt.internal.editors.layout.gre.NodeFactory; -import com.android.ide.eclipse.adt.internal.editors.layout.gre.RulesEngine; -import com.android.ide.eclipse.adt.internal.editors.layout.gre.ViewMetadataRepository; -import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode; -import com.android.ide.eclipse.adt.internal.editors.uimodel.UiDocumentNode; -import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode; -import com.android.ide.eclipse.adt.internal.lint.LintEditAction; -import com.android.resources.Density; - -import org.eclipse.core.filesystem.EFS; -import org.eclipse.core.filesystem.IFileStore; -import org.eclipse.core.resources.IFile; -import org.eclipse.core.resources.IWorkspaceRoot; -import org.eclipse.core.resources.ResourcesPlugin; -import org.eclipse.core.runtime.CoreException; -import org.eclipse.core.runtime.IPath; -import org.eclipse.core.runtime.QualifiedName; -import org.eclipse.jdt.internal.ui.javaeditor.EditorUtility; -import org.eclipse.jface.action.Action; -import org.eclipse.jface.action.ActionContributionItem; -import org.eclipse.jface.action.IAction; -import org.eclipse.jface.action.IContributionItem; -import org.eclipse.jface.action.IMenuManager; -import org.eclipse.jface.action.IStatusLineManager; -import org.eclipse.jface.action.MenuManager; -import org.eclipse.jface.action.Separator; -import org.eclipse.swt.SWT; -import org.eclipse.swt.custom.StyledText; -import org.eclipse.swt.dnd.DND; -import org.eclipse.swt.dnd.DragSource; -import org.eclipse.swt.dnd.DropTarget; -import org.eclipse.swt.dnd.TextTransfer; -import org.eclipse.swt.dnd.Transfer; -import org.eclipse.swt.events.ControlAdapter; -import org.eclipse.swt.events.ControlEvent; -import org.eclipse.swt.events.KeyEvent; -import org.eclipse.swt.events.MenuDetectEvent; -import org.eclipse.swt.events.MenuDetectListener; -import org.eclipse.swt.events.MouseEvent; -import org.eclipse.swt.events.PaintEvent; -import org.eclipse.swt.events.PaintListener; -import org.eclipse.swt.graphics.Color; -import org.eclipse.swt.graphics.Font; -import org.eclipse.swt.graphics.GC; -import org.eclipse.swt.graphics.Image; -import org.eclipse.swt.graphics.ImageData; -import org.eclipse.swt.graphics.Rectangle; -import org.eclipse.swt.widgets.Canvas; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Display; -import org.eclipse.swt.widgets.Menu; -import org.eclipse.swt.widgets.Shell; -import org.eclipse.ui.IActionBars; -import org.eclipse.ui.IEditorPart; -import org.eclipse.ui.IEditorSite; -import org.eclipse.ui.IWorkbenchPage; -import org.eclipse.ui.IWorkbenchWindow; -import org.eclipse.ui.PartInitException; -import org.eclipse.ui.actions.ActionFactory; -import org.eclipse.ui.actions.ActionFactory.IWorkbenchAction; -import org.eclipse.ui.actions.ContributionItemFactory; -import org.eclipse.ui.ide.IDE; -import org.eclipse.ui.internal.ide.IDEWorkbenchMessages; -import org.eclipse.ui.texteditor.ITextEditor; -import org.w3c.dom.Node; - -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -/** - * Displays the image rendered by the {@link GraphicalEditorPart} and handles - * the interaction with the widgets. - * <p/> - * {@link LayoutCanvas} implements the "Canvas" control. The editor part - * actually uses the {@link LayoutCanvasViewer}, which is a JFace viewer wrapper - * around this control. - * <p/> - * The LayoutCanvas contains the painting logic for the canvas. Selection, - * clipboard, view management etc. is handled in separate helper classes. - * - * @since GLE2 - */ -@SuppressWarnings("restriction") // For WorkBench "Show In" support -public class LayoutCanvas extends Canvas { - private final static QualifiedName NAME_ZOOM = - new QualifiedName(AdtPlugin.PLUGIN_ID, "zoom");//$NON-NLS-1$ - - private static final boolean DEBUG = false; - - static final String PREFIX_CANVAS_ACTION = "canvas_action_"; //$NON-NLS-1$ - - /** The layout editor that uses this layout canvas. */ - private final LayoutEditorDelegate mEditorDelegate; - - /** The Rules Engine, associated with the current project. */ - private RulesEngine mRulesEngine; - - /** GC wrapper given to the IViewRule methods. The GC itself is only defined in the - * context of {@link #onPaint(PaintEvent)}; otherwise it is null. */ - private GCWrapper mGCWrapper; - - /** Default font used on the canvas. Do not dispose, it's a system font. */ - private Font mFont; - - /** Current hover view info. Null when no mouse hover. */ - private CanvasViewInfo mHoverViewInfo; - - /** When true, always display the outline of all views. */ - private boolean mShowOutline; - - /** When true, display the outline of all empty parent views. */ - private boolean mShowInvisible; - - /** Drop target associated with this composite. */ - private DropTarget mDropTarget; - - /** Factory that can create {@link INode} proxies. */ - private final @NonNull NodeFactory mNodeFactory = new NodeFactory(this); - - /** Vertical scaling & scrollbar information. */ - private final CanvasTransform mVScale; - - /** Horizontal scaling & scrollbar information. */ - private final CanvasTransform mHScale; - - /** Drag source associated with this canvas. */ - private DragSource mDragSource; - - /** - * The current Outline Page, to set its model. - * It isn't possible to call OutlinePage2.dispose() in this.dispose(). - * this.dispose() is called from GraphicalEditorPart.dispose(), - * when page's widget is already disposed. - * Added the DisposeListener to OutlinePage2 in order to correctly dispose this page. - **/ - private OutlinePage mOutlinePage; - - /** Delete action for the Edit or context menu. */ - private Action mDeleteAction; - - /** Select-All action for the Edit or context menu. */ - private Action mSelectAllAction; - - /** Paste action for the Edit or context menu. */ - private Action mPasteAction; - - /** Cut action for the Edit or context menu. */ - private Action mCutAction; - - /** Copy action for the Edit or context menu. */ - private Action mCopyAction; - - /** Undo action: delegates to the text editor */ - private IAction mUndoAction; - - /** Redo action: delegates to the text editor */ - private IAction mRedoAction; - - /** Root of the context menu. */ - private MenuManager mMenuManager; - - /** The view hierarchy associated with this canvas. */ - private final ViewHierarchy mViewHierarchy = new ViewHierarchy(this); - - /** The selection in the canvas. */ - private final SelectionManager mSelectionManager = new SelectionManager(this); - - /** The overlay which paints the optional outline. */ - private OutlineOverlay mOutlineOverlay; - - /** The overlay which paints outlines around empty children */ - private EmptyViewsOverlay mEmptyOverlay; - - /** The overlay which paints the mouse hover. */ - private HoverOverlay mHoverOverlay; - - /** The overlay which paints the lint warnings */ - private LintOverlay mLintOverlay; - - /** The overlay which paints the selection. */ - private SelectionOverlay mSelectionOverlay; - - /** The overlay which paints the rendered layout image. */ - private ImageOverlay mImageOverlay; - - /** The overlay which paints masks hiding everything but included content. */ - private IncludeOverlay mIncludeOverlay; - - /** Configuration previews shown next to the layout */ - private final RenderPreviewManager mPreviewManager; - - /** - * Gesture Manager responsible for identifying mouse, keyboard and drag and - * drop events. - */ - private final GestureManager mGestureManager = new GestureManager(this); - - /** - * When set, performs a zoom-to-fit when the next rendering image arrives. - */ - private boolean mZoomFitNextImage; - - /** - * Native clipboard support. - */ - private ClipboardSupport mClipboardSupport; - - /** Tooltip manager for lint warnings */ - private LintTooltipManager mLintTooltipManager; - - private Color mBackgroundColor; - - /** - * Creates a new {@link LayoutCanvas} widget - * - * @param editorDelegate the associated editor delegate - * @param rulesEngine the rules engine - * @param parent parent SWT widget - * @param style the SWT style - */ - public LayoutCanvas(LayoutEditorDelegate editorDelegate, - RulesEngine rulesEngine, - Composite parent, - int style) { - super(parent, style | SWT.DOUBLE_BUFFERED | SWT.V_SCROLL | SWT.H_SCROLL); - mEditorDelegate = editorDelegate; - mRulesEngine = rulesEngine; - - mBackgroundColor = new Color(parent.getDisplay(), 150, 150, 150); - setBackground(mBackgroundColor); - - mClipboardSupport = new ClipboardSupport(this, parent); - mHScale = new CanvasTransform(this, getHorizontalBar()); - mVScale = new CanvasTransform(this, getVerticalBar()); - mPreviewManager = new RenderPreviewManager(this); - - // Unit test suite passes a null here; TODO: Replace with mocking - IFile file = editorDelegate != null ? editorDelegate.getEditor().getInputFile() : null; - if (file != null) { - String zoom = AdtPlugin.getFileProperty(file, NAME_ZOOM); - if (zoom != null) { - try { - double initialScale = Double.parseDouble(zoom); - if (initialScale > 0.1) { - mHScale.setScale(initialScale); - mVScale.setScale(initialScale); - } - } catch (NumberFormatException nfe) { - // Ignore - use zoom=100% - } - } else { - mZoomFitNextImage = true; - } - } - - mGCWrapper = new GCWrapper(mHScale, mVScale); - - Display display = getDisplay(); - mFont = display.getSystemFont(); - - // --- Set up graphic overlays - // mOutlineOverlay and mEmptyOverlay are initialized lazily - mHoverOverlay = new HoverOverlay(this, mHScale, mVScale); - mHoverOverlay.create(display); - mSelectionOverlay = new SelectionOverlay(this); - mSelectionOverlay.create(display); - mImageOverlay = new ImageOverlay(this, mHScale, mVScale); - mIncludeOverlay = new IncludeOverlay(this); - mImageOverlay.create(display); - mLintOverlay = new LintOverlay(this); - mLintOverlay.create(display); - - // --- Set up listeners - addPaintListener(new PaintListener() { - @Override - public void paintControl(PaintEvent e) { - onPaint(e); - } - }); - - addControlListener(new ControlAdapter() { - @Override - public void controlResized(ControlEvent e) { - super.controlResized(e); - - // Check editor state: - LayoutWindowCoordinator coordinator = null; - IEditorSite editorSite = getEditorDelegate().getEditor().getEditorSite(); - IWorkbenchWindow window = editorSite.getWorkbenchWindow(); - if (window != null) { - coordinator = LayoutWindowCoordinator.get(window, false); - if (coordinator != null) { - coordinator.syncMaximizedState(editorSite.getPage()); - } - } - - updateScrollBars(); - - // Update the zoom level in the canvas when you toggle the zoom - if (coordinator != null) { - mZoomCheck.run(); - } else { - // During startup, delay updates which can trigger further layout - getDisplay().asyncExec(mZoomCheck); - - } - } - }); - - // --- setup drag'n'drop --- - // DND Reference: http://www.eclipse.org/articles/Article-SWT-DND/DND-in-SWT.html - - mDropTarget = createDropTarget(this); - mDragSource = createDragSource(this); - mGestureManager.registerListeners(mDragSource, mDropTarget); - - if (mEditorDelegate == null) { - // TODO: In another CL we should use EasyMock/objgen to provide an editor. - return; // Unit test - } - - // --- setup context menu --- - setupGlobalActionHandlers(); - createContextMenu(); - - // --- setup outline --- - // Get the outline associated with this editor, if any and of the right type. - if (editorDelegate != null) { - mOutlinePage = editorDelegate.getGraphicalOutline(); - } - - mLintTooltipManager = new LintTooltipManager(this); - mLintTooltipManager.register(); - } - - void updateScrollBars() { - Rectangle clientArea = getClientArea(); - Image image = mImageOverlay.getImage(); - if (image != null) { - ImageData imageData = image.getImageData(); - int clientWidth = clientArea.width; - int clientHeight = clientArea.height; - - int imageWidth = imageData.width; - int imageHeight = imageData.height; - - int fullWidth = imageWidth; - int fullHeight = imageHeight; - - if (mPreviewManager.hasPreviews()) { - fullHeight = Math.max(fullHeight, - (int) (mPreviewManager.getHeight() / mHScale.getScale())); - } - - if (clientWidth == 0) { - clientWidth = imageWidth; - Shell shell = getShell(); - if (shell != null) { - org.eclipse.swt.graphics.Point size = shell.getSize(); - if (size.x > 0) { - clientWidth = size.x * 70 / 100; - } - } - } - if (clientHeight == 0) { - clientHeight = imageHeight; - Shell shell = getShell(); - if (shell != null) { - org.eclipse.swt.graphics.Point size = shell.getSize(); - if (size.y > 0) { - clientWidth = size.y * 80 / 100; - } - } - } - - mHScale.setSize(imageWidth, fullWidth, clientWidth); - mVScale.setSize(imageHeight, fullHeight, clientHeight); - } - } - - private Runnable mZoomCheck = new Runnable() { - private Boolean mWasZoomed; - - @Override - public void run() { - if (isDisposed()) { - return; - } - - IEditorSite editorSite = getEditorDelegate().getEditor().getEditorSite(); - IWorkbenchWindow window = editorSite.getWorkbenchWindow(); - if (window != null) { - LayoutWindowCoordinator coordinator = LayoutWindowCoordinator.get(window, false); - if (coordinator != null) { - Boolean zoomed = coordinator.isEditorMaximized(); - if (mWasZoomed != zoomed) { - if (mWasZoomed != null) { - LayoutActionBar actionBar = getGraphicalEditor().getLayoutActionBar(); - if (actionBar.isZoomingAllowed()) { - setFitScale(true /*onlyZoomOut*/, true /*allowZoomIn*/); - } - } - mWasZoomed = zoomed; - } - } - } - } - }; - - void handleKeyPressed(KeyEvent e) { - // Set up backspace as an alias for the delete action within the canvas. - // On most Macs there is no delete key - though there IS a key labeled - // "Delete" and it sends a backspace key code! In short, for Macs we should - // treat backspace as delete, and it's harmless (and probably useful) to - // handle backspace for other platforms as well. - if (e.keyCode == SWT.BS) { - mDeleteAction.run(); - } else if (e.keyCode == SWT.ESC) { - mSelectionManager.selectParent(); - } else if (e.keyCode == DynamicContextMenu.DEFAULT_ACTION_KEY) { - mSelectionManager.performDefaultAction(); - } else if (e.keyCode == 'r') { - // Keep key bindings in sync with {@link DynamicContextMenu#createPlainAction} - // TODO: Find a way to look up the Eclipse key bindings and attempt - // to use the current keymap's rename action. - if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_DARWIN) { - // Command+Option+R - if ((e.stateMask & (SWT.MOD1 | SWT.MOD3)) == (SWT.MOD1 | SWT.MOD3)) { - mSelectionManager.performRename(); - } - } else { - // Alt+Shift+R - if ((e.stateMask & (SWT.MOD2 | SWT.MOD3)) == (SWT.MOD2 | SWT.MOD3)) { - mSelectionManager.performRename(); - } - } - } else { - // Zooming actions - char c = e.character; - LayoutActionBar actionBar = getGraphicalEditor().getLayoutActionBar(); - if (c == '1' && actionBar.isZoomingAllowed()) { - setScale(1, true); - } else if (c == '0' && actionBar.isZoomingAllowed()) { - setFitScale(true, true /*allowZoomIn*/); - } else if (e.keyCode == '0' && (e.stateMask & SWT.MOD2) != 0 - && actionBar.isZoomingAllowed()) { - setFitScale(false, true /*allowZoomIn*/); - } else if ((c == '+' || c == '=') && actionBar.isZoomingAllowed()) { - if ((e.stateMask & SWT.MOD1) != 0) { - mPreviewManager.zoomIn(); - } else { - actionBar.rescale(1); - } - } else if (c == '-' && actionBar.isZoomingAllowed()) { - if ((e.stateMask & SWT.MOD1) != 0) { - mPreviewManager.zoomOut(); - } else { - actionBar.rescale(-1); - } - } - } - } - - @Override - public void dispose() { - super.dispose(); - - mGestureManager.unregisterListeners(mDragSource, mDropTarget); - - if (mLintTooltipManager != null) { - mLintTooltipManager.unregister(); - mLintTooltipManager = null; - } - - if (mDropTarget != null) { - mDropTarget.dispose(); - mDropTarget = null; - } - - if (mRulesEngine != null) { - mRulesEngine.dispose(); - mRulesEngine = null; - } - - if (mDragSource != null) { - mDragSource.dispose(); - mDragSource = null; - } - - if (mClipboardSupport != null) { - mClipboardSupport.dispose(); - mClipboardSupport = null; - } - - if (mGCWrapper != null) { - mGCWrapper.dispose(); - mGCWrapper = null; - } - - if (mOutlineOverlay != null) { - mOutlineOverlay.dispose(); - mOutlineOverlay = null; - } - - if (mEmptyOverlay != null) { - mEmptyOverlay.dispose(); - mEmptyOverlay = null; - } - - if (mHoverOverlay != null) { - mHoverOverlay.dispose(); - mHoverOverlay = null; - } - - if (mSelectionOverlay != null) { - mSelectionOverlay.dispose(); - mSelectionOverlay = null; - } - - if (mImageOverlay != null) { - mImageOverlay.dispose(); - mImageOverlay = null; - } - - if (mIncludeOverlay != null) { - mIncludeOverlay.dispose(); - mIncludeOverlay = null; - } - - if (mLintOverlay != null) { - mLintOverlay.dispose(); - mLintOverlay = null; - } - - if (mBackgroundColor != null) { - mBackgroundColor.dispose(); - mBackgroundColor = null; - } - - mPreviewManager.disposePreviews(); - mViewHierarchy.dispose(); - } - - /** - * Returns the configuration preview manager for this canvas - * - * @return the configuration preview manager for this canvas - */ - @NonNull - public RenderPreviewManager getPreviewManager() { - return mPreviewManager; - } - - /** Returns the Rules Engine, associated with the current project. */ - RulesEngine getRulesEngine() { - return mRulesEngine; - } - - /** Sets the Rules Engine, associated with the current project. */ - void setRulesEngine(RulesEngine rulesEngine) { - mRulesEngine = rulesEngine; - } - - /** - * Returns the factory to use to convert from {@link CanvasViewInfo} or from - * {@link UiViewElementNode} to {@link INode} proxies. - * - * @return the node factory - */ - @NonNull - public NodeFactory getNodeFactory() { - return mNodeFactory; - } - - /** - * Returns the GCWrapper used to paint view rules. - * - * @return The GCWrapper used to paint view rules - */ - GCWrapper getGcWrapper() { - return mGCWrapper; - } - - /** - * Returns the {@link LayoutEditorDelegate} associated with this canvas. - * - * @return the delegate - */ - public LayoutEditorDelegate getEditorDelegate() { - return mEditorDelegate; - } - - /** - * Returns the current {@link ImageOverlay} painting the rendered result - * - * @return the image overlay responsible for painting the rendered result, never null - */ - ImageOverlay getImageOverlay() { - return mImageOverlay; - } - - /** - * Returns the current {@link SelectionOverlay} painting the selection highlights - * - * @return the selection overlay responsible for painting the selection highlights, - * never null - */ - SelectionOverlay getSelectionOverlay() { - return mSelectionOverlay; - } - - /** - * Returns the {@link GestureManager} associated with this canvas. - * - * @return the {@link GestureManager} associated with this canvas, never null. - */ - GestureManager getGestureManager() { - return mGestureManager; - } - - /** - * Returns the current {@link HoverOverlay} painting the mouse hover. - * - * @return the hover overlay responsible for painting the mouse hover, - * never null - */ - HoverOverlay getHoverOverlay() { - return mHoverOverlay; - } - - /** - * Returns the horizontal {@link CanvasTransform} transform object, which can map - * a layout point into a control point. - * - * @return A {@link CanvasTransform} for mapping between layout and control - * coordinates in the horizontal dimension. - */ - CanvasTransform getHorizontalTransform() { - return mHScale; - } - - /** - * Returns the vertical {@link CanvasTransform} transform object, which can map a - * layout point into a control point. - * - * @return A {@link CanvasTransform} for mapping between layout and control - * coordinates in the vertical dimension. - */ - CanvasTransform getVerticalTransform() { - return mVScale; - } - - /** - * Returns the {@link OutlinePage} associated with this canvas - * - * @return the {@link OutlinePage} associated with this canvas - */ - public OutlinePage getOutlinePage() { - return mOutlinePage; - } - - /** - * Returns the {@link SelectionManager} associated with this canvas. - * - * @return The {@link SelectionManager} holding the selection for this - * canvas. Never null. - */ - public SelectionManager getSelectionManager() { - return mSelectionManager; - } - - /** - * Returns the {@link ViewHierarchy} object associated with this canvas, - * holding the most recent rendered view of the scene, if valid. - * - * @return The {@link ViewHierarchy} object associated with this canvas. - * Never null. - */ - public ViewHierarchy getViewHierarchy() { - return mViewHierarchy; - } - - /** - * Returns the {@link ClipboardSupport} object associated with this canvas. - * - * @return The {@link ClipboardSupport} object for this canvas. Null only after dispose. - */ - public ClipboardSupport getClipboardSupport() { - return mClipboardSupport; - } - - /** Returns the Select All action bound to this canvas */ - Action getSelectAllAction() { - return mSelectAllAction; - } - - /** Returns the associated {@link GraphicalEditorPart} */ - GraphicalEditorPart getGraphicalEditor() { - return mEditorDelegate.getGraphicalEditor(); - } - - /** - * Sets the result of the layout rendering. The result object indicates if the layout - * rendering succeeded. If it did, it contains a bitmap and the objects rectangles. - * - * Implementation detail: the bridge's computeLayout() method already returns a newly - * allocated ILayourResult. That means we can keep this result and hold on to it - * when it is valid. - * - * @param session The new scene, either valid or not. - * @param explodedNodes The set of individual nodes the layout computer was asked to - * explode. Note that these are independent of the explode-all mode where - * all views are exploded; this is used only for the mode ( - * {@link #showInvisibleViews(boolean)}) where individual invisible nodes - * are padded during certain interactions. - */ - void setSession(RenderSession session, Set<UiElementNode> explodedNodes, - boolean layoutlib5) { - // disable any hover - clearHover(); - - mViewHierarchy.setSession(session, explodedNodes, layoutlib5); - if (mViewHierarchy.isValid() && session != null) { - Image image = mImageOverlay.setImage(session.getImage(), - session.isAlphaChannelImage()); - - mOutlinePage.setModel(mViewHierarchy.getRoot()); - getGraphicalEditor().setModel(mViewHierarchy.getRoot()); - - if (image != null) { - updateScrollBars(); - if (mZoomFitNextImage) { - // Must be run asynchronously because getClientArea() returns 0 bounds - // when the editor is being initialized - getDisplay().asyncExec(new Runnable() { - @Override - public void run() { - if (!isDisposed()) { - ensureZoomed(); - } - } - }); - } - - // Ensure that if we have a a preview mode enabled, it's shown - syncPreviewMode(); - } - } - - redraw(); - } - - void ensureZoomed() { - if (mZoomFitNextImage && getClientArea().height > 0) { - mZoomFitNextImage = false; - LayoutActionBar actionBar = getGraphicalEditor().getLayoutActionBar(); - if (actionBar.isZoomingAllowed()) { - setFitScale(true, true /*allowZoomIn*/); - } - } - } - - void setShowOutline(boolean newState) { - mShowOutline = newState; - redraw(); - } - - /** - * Returns the zoom scale factor of the canvas (the amount the full - * resolution render of the device is zoomed before being shown on the - * canvas) - * - * @return the image scale - */ - public double getScale() { - return mHScale.getScale(); - } - - void setScale(double scale, boolean redraw) { - if (scale <= 0.0) { - scale = 1.0; - } - - if (scale == getScale()) { - return; - } - - mHScale.setScale(scale); - mVScale.setScale(scale); - if (redraw) { - redraw(); - } - - // Clear the zoom setting if it is almost identical to 1.0 - String zoomValue = (Math.abs(scale - 1.0) < 0.0001) ? null : Double.toString(scale); - IFile file = mEditorDelegate.getEditor().getInputFile(); - if (file != null) { - AdtPlugin.setFileProperty(file, NAME_ZOOM, zoomValue); - } - } - - /** - * Scales the canvas to best fit - * - * @param onlyZoomOut if true, then the zooming factor will never be larger than 1, - * which means that this function will zoom out if necessary to show the - * rendered image, but it will never zoom in. - * TODO: Rename this, it sounds like it conflicts with allowZoomIn, - * even though one is referring to the zoom level and one is referring - * to the overall act of scaling above/below 1. - * @param allowZoomIn if false, then if the computed zoom factor is smaller than - * the current zoom factor, it will be ignored. - */ - public void setFitScale(boolean onlyZoomOut, boolean allowZoomIn) { - ImageOverlay imageOverlay = getImageOverlay(); - if (imageOverlay == null) { - return; - } - Image image = imageOverlay.getImage(); - if (image != null) { - Rectangle canvasSize = getClientArea(); - int canvasWidth = canvasSize.width; - int canvasHeight = canvasSize.height; - - boolean hasPreviews = mPreviewManager.hasPreviews(); - if (hasPreviews) { - canvasWidth = 2 * canvasWidth / 3; - } else { - canvasWidth -= 4; - canvasHeight -= 4; - } - - ImageData imageData = image.getImageData(); - int sceneWidth = imageData.width; - int sceneHeight = imageData.height; - if (sceneWidth == 0.0 || sceneHeight == 0.0) { - return; - } - - if (imageOverlay.getShowDropShadow()) { - sceneWidth += 2 * ImageUtils.SHADOW_SIZE; - sceneHeight += 2 * ImageUtils.SHADOW_SIZE; - } - - // Reduce the margins if necessary - int hDelta = canvasWidth - sceneWidth; - int hMargin = 0; - if (hDelta > 2 * CanvasTransform.DEFAULT_MARGIN) { - hMargin = CanvasTransform.DEFAULT_MARGIN; - } else if (hDelta > 0) { - hMargin = hDelta / 2; - } - - int vDelta = canvasHeight - sceneHeight; - int vMargin = 0; - if (vDelta > 2 * CanvasTransform.DEFAULT_MARGIN) { - vMargin = CanvasTransform.DEFAULT_MARGIN; - } else if (vDelta > 0) { - vMargin = vDelta / 2; - } - - double hScale = (canvasWidth - 2 * hMargin) / (double) sceneWidth; - double vScale = (canvasHeight - 2 * vMargin) / (double) sceneHeight; - - double scale = Math.min(hScale, vScale); - - if (onlyZoomOut) { - scale = Math.min(1.0, scale); - } - - if (!allowZoomIn && scale > getScale()) { - return; - } - - setScale(scale, true); - } - } - - /** - * Transforms a point, expressed in layout coordinates, into "client" coordinates - * relative to the control (and not relative to the display). - * - * @param canvasX X in the canvas coordinates - * @param canvasY Y in the canvas coordinates - * @return A new {@link Point} in control client coordinates (not display coordinates) - */ - Point layoutToControlPoint(int canvasX, int canvasY) { - int x = mHScale.translate(canvasX); - int y = mVScale.translate(canvasY); - return new Point(x, y); - } - - /** - * Returns the action for the context menu corresponding to the given action id. - * <p/> - * For global actions such as copy or paste, the action id must be composed of - * the {@link #PREFIX_CANVAS_ACTION} followed by one of {@link ActionFactory}'s - * action ids. - * <p/> - * Returns null if there's no action for the given id. - */ - IAction getAction(String actionId) { - String prefix = PREFIX_CANVAS_ACTION; - if (mMenuManager == null || - actionId == null || - !actionId.startsWith(prefix)) { - return null; - } - - actionId = actionId.substring(prefix.length()); - - for (IContributionItem contrib : mMenuManager.getItems()) { - if (contrib instanceof ActionContributionItem && - actionId.equals(contrib.getId())) { - return ((ActionContributionItem) contrib).getAction(); - } - } - - return null; - } - - //--------------- - - /** - * Paints the canvas in response to paint events. - */ - private void onPaint(PaintEvent e) { - GC gc = e.gc; - gc.setFont(mFont); - mGCWrapper.setGC(gc); - try { - if (!mImageOverlay.isHiding()) { - mImageOverlay.paint(gc); - } - - mPreviewManager.paint(gc); - - if (mShowOutline) { - if (mOutlineOverlay == null) { - mOutlineOverlay = new OutlineOverlay(mViewHierarchy, mHScale, mVScale); - mOutlineOverlay.create(getDisplay()); - } - if (!mOutlineOverlay.isHiding()) { - mOutlineOverlay.paint(gc); - } - } - - if (mShowInvisible) { - if (mEmptyOverlay == null) { - mEmptyOverlay = new EmptyViewsOverlay(mViewHierarchy, mHScale, mVScale); - mEmptyOverlay.create(getDisplay()); - } - if (!mEmptyOverlay.isHiding()) { - mEmptyOverlay.paint(gc); - } - } - - if (!mHoverOverlay.isHiding()) { - mHoverOverlay.paint(gc); - } - - if (!mLintOverlay.isHiding()) { - mLintOverlay.paint(gc); - } - - if (!mIncludeOverlay.isHiding()) { - mIncludeOverlay.paint(gc); - } - - if (!mSelectionOverlay.isHiding()) { - mSelectionOverlay.paint(mSelectionManager, mGCWrapper, gc, mRulesEngine); - } - mGestureManager.paint(gc); - - } finally { - mGCWrapper.setGC(null); - } - } - - /** - * Shows or hides invisible parent views, which are views which have empty bounds and - * no children. The nodes which will be shown are provided by - * {@link #getNodesToExplode()}. - * - * @param show When true, any invisible parent nodes are padded and highlighted - * ("exploded"), and when false any formerly exploded nodes are hidden. - */ - void showInvisibleViews(boolean show) { - if (mShowInvisible == show) { - return; - } - mShowInvisible = show; - - // Optimization: Avoid doing work when we don't have invisible parents (on show) - // or formerly exploded nodes (on hide). - if (show && !mViewHierarchy.hasInvisibleParents()) { - return; - } else if (!show && !mViewHierarchy.hasExplodedParents()) { - return; - } - - mEditorDelegate.recomputeLayout(); - } - - /** - * Returns a set of nodes that should be exploded (forced non-zero padding during render), - * or null if no nodes should be exploded. (Note that this is independent of the - * explode-all mode, where all nodes are padded -- that facility does not use this - * mechanism, which is only intended to be used to expose invisible parent nodes. - * - * @return The set of invisible parents, or null if no views should be expanded. - */ - public Set<UiElementNode> getNodesToExplode() { - if (mShowInvisible) { - return mViewHierarchy.getInvisibleNodes(); - } - - // IF we have selection, and IF we have invisible nodes in the view, - // see if any of the selected items are among the invisible nodes, and if so - // add them to a lazily constructed set which we pass back for rendering. - Set<UiElementNode> result = null; - List<SelectionItem> selections = mSelectionManager.getSelections(); - if (selections.size() > 0) { - List<CanvasViewInfo> invisibleParents = mViewHierarchy.getInvisibleViews(); - if (invisibleParents.size() > 0) { - for (SelectionItem item : selections) { - CanvasViewInfo viewInfo = item.getViewInfo(); - // O(n^2) here, but both the selection size and especially the - // invisibleParents size are expected to be small - if (invisibleParents.contains(viewInfo)) { - UiViewElementNode node = viewInfo.getUiViewNode(); - if (node != null) { - if (result == null) { - result = new HashSet<UiElementNode>(); - } - result.add(node); - } - } - } - } - } - - return result; - } - - /** - * Clears the hover. - */ - void clearHover() { - mHoverOverlay.clearHover(); - } - - /** - * Hover on top of a known child. - */ - void hover(MouseEvent e) { - // Check if a button is pressed; no hovers during drags - if ((e.stateMask & SWT.BUTTON_MASK) != 0) { - clearHover(); - return; - } - - LayoutPoint p = ControlPoint.create(this, e).toLayout(); - CanvasViewInfo vi = mViewHierarchy.findViewInfoAt(p); - - // We don't hover on the root since it's not a widget per see and it is always there. - // We also skip spacers... - if (vi != null && (vi.isRoot() || vi.isHidden())) { - vi = null; - } - - boolean needsUpdate = vi != mHoverViewInfo; - mHoverViewInfo = vi; - - if (vi == null) { - clearHover(); - } else { - Rectangle r = vi.getSelectionRect(); - mHoverOverlay.setHover(r.x, r.y, r.width, r.height); - } - - if (needsUpdate) { - redraw(); - } - } - - /** - * Shows the given {@link CanvasViewInfo}, which can mean exposing its XML or if it's - * an included element, its corresponding file. - * - * @param vi the {@link CanvasViewInfo} to be shown - */ - public void show(CanvasViewInfo vi) { - String url = vi.getIncludeUrl(); - if (url != null) { - showInclude(url); - } else { - showXml(vi); - } - } - - /** - * Shows the layout file referenced by the given url in the same project. - * - * @param url The layout attribute url of the form @layout/foo - */ - private void showInclude(String url) { - GraphicalEditorPart graphicalEditor = getGraphicalEditor(); - IPath filePath = graphicalEditor.findResourceFile(url); - if (filePath == null) { - // Should not be possible - if the URL had been bad, then we wouldn't - // have been able to render the scene and you wouldn't have been able - // to click on it - return; - } - - // Save the including file, if necessary: without it, the "Show Included In" - // facility which is invoked automatically will not work properly if the <include> - // tag is not in the saved version of the file, since the outer file is read from - // disk rather than from memory. - IEditorSite editorSite = graphicalEditor.getEditorSite(); - IWorkbenchPage page = editorSite.getPage(); - page.saveEditor(mEditorDelegate.getEditor(), false); - - IWorkspaceRoot workspace = ResourcesPlugin.getWorkspace().getRoot(); - IFile xmlFile = null; - IPath workspacePath = workspace.getLocation(); - if (workspacePath.isPrefixOf(filePath)) { - IPath relativePath = filePath.makeRelativeTo(workspacePath); - xmlFile = (IFile) workspace.findMember(relativePath); - } else if (filePath.isAbsolute()) { - xmlFile = workspace.getFileForLocation(filePath); - } - if (xmlFile != null) { - IFile leavingFile = graphicalEditor.getEditedFile(); - Reference next = Reference.create(graphicalEditor.getEditedFile()); - - try { - IEditorPart openAlready = EditorUtility.isOpenInEditor(xmlFile); - - // Show the included file as included within this click source? - if (openAlready != null) { - LayoutEditorDelegate delegate = LayoutEditorDelegate.fromEditor(openAlready); - if (delegate != null) { - GraphicalEditorPart gEditor = delegate.getGraphicalEditor(); - if (gEditor != null && - gEditor.renderingSupports(Capability.EMBEDDED_LAYOUT)) { - gEditor.showIn(next); - } - } - } else { - try { - // Set initial state of a new file - // TODO: Only set rendering target portion of the state - String state = ConfigurationDescription.getDescription(leavingFile); - xmlFile.setSessionProperty(GraphicalEditorPart.NAME_INITIAL_STATE, - state); - } catch (CoreException e) { - // pass - } - - if (graphicalEditor.renderingSupports(Capability.EMBEDDED_LAYOUT)) { - try { - xmlFile.setSessionProperty(GraphicalEditorPart.NAME_INCLUDE, next); - } catch (CoreException e) { - // pass - worst that can happen is that we don't - //start with inclusion - } - } - } - - EditorUtility.openInEditor(xmlFile, true); - return; - } catch (PartInitException ex) { - AdtPlugin.log(ex, "Can't open %$1s", url); //$NON-NLS-1$ - } - } else { - // It's not a path in the workspace; look externally - // (this is probably an @android: path) - if (filePath.isAbsolute()) { - IFileStore fileStore = EFS.getLocalFileSystem().getStore(filePath); - // fileStore = fileStore.getChild(names[i]); - if (!fileStore.fetchInfo().isDirectory() && fileStore.fetchInfo().exists()) { - try { - IDE.openEditorOnFileStore(page, fileStore); - return; - } catch (PartInitException ex) { - AdtPlugin.log(ex, "Can't open %$1s", url); //$NON-NLS-1$ - } - } - } - } - - // Failed: display message to the user - String message = String.format("Could not find resource %1$s", url); - IStatusLineManager status = editorSite.getActionBars().getStatusLineManager(); - status.setErrorMessage(message); - getDisplay().beep(); - } - - /** - * Returns the layout resource name of this layout - * - * @return the layout resource name of this layout - */ - public String getLayoutResourceName() { - GraphicalEditorPart graphicalEditor = getGraphicalEditor(); - return graphicalEditor.getLayoutResourceName(); - } - - /** - * Returns the layout resource url of the current layout - * - * @return - */ - /* - public String getMe() { - GraphicalEditorPart graphicalEditor = getGraphicalEditor(); - IFile editedFile = graphicalEditor.getEditedFile(); - return editedFile.getProjectRelativePath().toOSString(); - } - */ - - /** - * Show the XML element corresponding to the given {@link CanvasViewInfo} (unless it's - * a root). - * - * @param vi The clicked {@link CanvasViewInfo} whose underlying XML element we want - * to view - */ - private void showXml(CanvasViewInfo vi) { - // Warp to the text editor and show the corresponding XML for the - // double-clicked widget - if (vi.isRoot()) { - return; - } - - Node xmlNode = vi.getXmlNode(); - if (xmlNode != null) { - boolean found = mEditorDelegate.getEditor().show(xmlNode); - if (!found) { - getDisplay().beep(); - } - } - } - - //--------------- - - /** - * Helper to create the drag source for the given control. - * <p/> - * This is static with package-access so that {@link OutlinePage} can also - * create an exact copy of the source with the same attributes. - */ - /* package */static DragSource createDragSource(Control control) { - DragSource source = new DragSource(control, DND.DROP_COPY | DND.DROP_MOVE); - source.setTransfer(new Transfer[] { - TextTransfer.getInstance(), - SimpleXmlTransfer.getInstance() - }); - return source; - } - - /** - * Helper to create the drop target for the given control. - */ - private static DropTarget createDropTarget(Control control) { - DropTarget dropTarget = new DropTarget( - control, DND.DROP_COPY | DND.DROP_MOVE | DND.DROP_DEFAULT); - dropTarget.setTransfer(new Transfer[] { - SimpleXmlTransfer.getInstance() - }); - return dropTarget; - } - - //--------------- - - /** - * Invoked by the constructor to add our cut/copy/paste/delete/select-all - * handlers in the global action handlers of this editor's site. - * <p/> - * This will enable the menu items under the global Edit menu and make them - * invoke our actions as needed. As a benefit, the corresponding shortcut - * accelerators will do what one would expect. - */ - private void setupGlobalActionHandlers() { - mCutAction = new Action() { - @Override - public void run() { - mClipboardSupport.cutSelectionToClipboard(mSelectionManager.getSnapshot()); - updateMenuActionState(); - } - }; - - copyActionAttributes(mCutAction, ActionFactory.CUT); - - mCopyAction = new Action() { - @Override - public void run() { - mClipboardSupport.copySelectionToClipboard(mSelectionManager.getSnapshot()); - updateMenuActionState(); - } - }; - - copyActionAttributes(mCopyAction, ActionFactory.COPY); - - mPasteAction = new Action() { - @Override - public void run() { - mClipboardSupport.pasteSelection(mSelectionManager.getSnapshot()); - updateMenuActionState(); - } - }; - - copyActionAttributes(mPasteAction, ActionFactory.PASTE); - - mDeleteAction = new Action() { - @Override - public void run() { - mClipboardSupport.deleteSelection( - getDeleteLabel(), - mSelectionManager.getSnapshot()); - } - }; - - copyActionAttributes(mDeleteAction, ActionFactory.DELETE); - - mSelectAllAction = new Action() { - @Override - public void run() { - GraphicalEditorPart graphicalEditor = getEditorDelegate().getGraphicalEditor(); - StyledText errorLabel = graphicalEditor.getErrorLabel(); - if (errorLabel.isFocusControl()) { - errorLabel.selectAll(); - return; - } - - mSelectionManager.selectAll(); - } - }; - - copyActionAttributes(mSelectAllAction, ActionFactory.SELECT_ALL); - } - - String getCutLabel() { - return mCutAction.getText(); - } - - String getDeleteLabel() { - // verb "Delete" from the DELETE action's title - return mDeleteAction.getText(); - } - - /** - * Updates menu actions that depends on the selection. - */ - void updateMenuActionState() { - List<SelectionItem> selections = getSelectionManager().getSelections(); - boolean hasSelection = !selections.isEmpty(); - if (hasSelection && selections.size() == 1 && selections.get(0).isRoot()) { - hasSelection = false; - } - - StyledText errorLabel = getGraphicalEditor().getErrorLabel(); - mCutAction.setEnabled(hasSelection); - mCopyAction.setEnabled(hasSelection || errorLabel.getSelectionCount() > 0); - mDeleteAction.setEnabled(hasSelection); - // Select All should *always* be selectable, regardless of whether anything - // is currently selected. - mSelectAllAction.setEnabled(true); - - // The paste operation is only available if we can paste our custom type. - // We do not currently support pasting random text (e.g. XML). Maybe later. - boolean hasSxt = mClipboardSupport.hasSxtOnClipboard(); - mPasteAction.setEnabled(hasSxt); - } - - /** - * Update the actions when this editor is activated - * - * @param bars the action bar for this canvas - */ - public void updateGlobalActions(@NonNull IActionBars bars) { - updateMenuActionState(); - - ITextEditor editor = mEditorDelegate.getEditor().getStructuredTextEditor(); - boolean graphical = getEditorDelegate().getEditor().getActivePage() == 0; - if (graphical) { - bars.setGlobalActionHandler(ActionFactory.CUT.getId(), mCutAction); - bars.setGlobalActionHandler(ActionFactory.COPY.getId(), mCopyAction); - bars.setGlobalActionHandler(ActionFactory.PASTE.getId(), mPasteAction); - bars.setGlobalActionHandler(ActionFactory.DELETE.getId(), mDeleteAction); - bars.setGlobalActionHandler(ActionFactory.SELECT_ALL.getId(), mSelectAllAction); - - // Delegate the Undo and Redo actions to the text editor ones, but wrap them - // such that we run lint to update the results on the current page (this is - // normally done on each editor operation that goes through - // {@link AndroidXmlEditor#wrapUndoEditXmlModel}, but not undo/redo) - if (mUndoAction == null) { - IAction undoAction = editor.getAction(ActionFactory.UNDO.getId()); - mUndoAction = new LintEditAction(undoAction, getEditorDelegate().getEditor()); - } - bars.setGlobalActionHandler(ActionFactory.UNDO.getId(), mUndoAction); - if (mRedoAction == null) { - IAction redoAction = editor.getAction(ActionFactory.REDO.getId()); - mRedoAction = new LintEditAction(redoAction, getEditorDelegate().getEditor()); - } - bars.setGlobalActionHandler(ActionFactory.REDO.getId(), mRedoAction); - } else { - bars.setGlobalActionHandler(ActionFactory.CUT.getId(), - editor.getAction(ActionFactory.CUT.getId())); - bars.setGlobalActionHandler(ActionFactory.COPY.getId(), - editor.getAction(ActionFactory.COPY.getId())); - bars.setGlobalActionHandler(ActionFactory.PASTE.getId(), - editor.getAction(ActionFactory.PASTE.getId())); - bars.setGlobalActionHandler(ActionFactory.DELETE.getId(), - editor.getAction(ActionFactory.DELETE.getId())); - bars.setGlobalActionHandler(ActionFactory.SELECT_ALL.getId(), - editor.getAction(ActionFactory.SELECT_ALL.getId())); - bars.setGlobalActionHandler(ActionFactory.UNDO.getId(), - editor.getAction(ActionFactory.UNDO.getId())); - bars.setGlobalActionHandler(ActionFactory.REDO.getId(), - editor.getAction(ActionFactory.REDO.getId())); - } - - bars.updateActionBars(); - } - - /** - * Helper for {@link #setupGlobalActionHandlers()}. - * Copies the action attributes form the given {@link ActionFactory}'s action to - * our action. - * <p/> - * {@link ActionFactory} provides access to the standard global actions in Eclipse. - * <p/> - * This allows us to grab the standard labels and icons for the - * global actions such as copy, cut, paste, delete and select-all. - */ - private void copyActionAttributes(Action action, ActionFactory factory) { - IWorkbenchAction wa = factory.create( - mEditorDelegate.getEditor().getEditorSite().getWorkbenchWindow()); - action.setId(wa.getId()); - action.setText(wa.getText()); - action.setEnabled(wa.isEnabled()); - action.setDescription(wa.getDescription()); - action.setToolTipText(wa.getToolTipText()); - action.setAccelerator(wa.getAccelerator()); - action.setActionDefinitionId(wa.getActionDefinitionId()); - action.setImageDescriptor(wa.getImageDescriptor()); - action.setHoverImageDescriptor(wa.getHoverImageDescriptor()); - action.setDisabledImageDescriptor(wa.getDisabledImageDescriptor()); - action.setHelpListener(wa.getHelpListener()); - } - - /** - * Creates the context menu for the canvas. This is called once from the canvas' constructor. - * <p/> - * The menu has a static part with actions that are always available such as - * copy, cut, paste and show in > explorer. This is created by - * {@link #setupStaticMenuActions(IMenuManager)}. - * <p/> - * There's also a dynamic part that is populated by the rules of the - * selected elements, created by {@link DynamicContextMenu}. - */ - @SuppressWarnings("unused") - private void createContextMenu() { - - // This manager is the root of the context menu. - mMenuManager = new MenuManager() { - @Override - public boolean isDynamic() { - return true; - } - }; - - // Fill the menu manager with the static & dynamic actions - setupStaticMenuActions(mMenuManager); - new DynamicContextMenu(mEditorDelegate, this, mMenuManager); - Menu menu = mMenuManager.createContextMenu(this); - setMenu(menu); - - // Add listener to detect when the menu is about to be posted, such that - // we can sync the selection. Without this, you can right click on something - // in the canvas which is NOT selected, and the context menu will show items related - // to the selection, NOT the item you clicked on!! - addMenuDetectListener(new MenuDetectListener() { - @Override - public void menuDetected(MenuDetectEvent e) { - mSelectionManager.menuClick(e); - } - }); - } - - /** - * Invoked by {@link #createContextMenu()} to create our *static* context menu once. - * <p/> - * The content of the menu itself does not change. However the state of the - * various items is controlled by their associated actions. - * <p/> - * For cut/copy/paste/delete/select-all, we explicitly reuse the actions - * created by {@link #setupGlobalActionHandlers()}, so this method must be - * invoked after that one. - */ - private void setupStaticMenuActions(IMenuManager manager) { - manager.removeAll(); - - manager.add(new SelectionManager.SelectionMenu(getGraphicalEditor())); - manager.add(new Separator()); - manager.add(mCutAction); - manager.add(mCopyAction); - manager.add(mPasteAction); - manager.add(new Separator()); - manager.add(mDeleteAction); - manager.add(new Separator()); - manager.add(new PlayAnimationMenu(this)); - manager.add(new ExportScreenshotAction(this)); - manager.add(new Separator()); - - // Group "Show Included In" and "Show In" together - manager.add(new ShowWithinMenu(mEditorDelegate)); - - // Create a "Show In" sub-menu and automatically populate it using standard - // actions contributed by the workbench. - String showInLabel = IDEWorkbenchMessages.Workbench_showIn; - MenuManager showInSubMenu = new MenuManager(showInLabel); - showInSubMenu.add( - ContributionItemFactory.VIEWS_SHOW_IN.create( - mEditorDelegate.getEditor().getSite().getWorkbenchWindow())); - manager.add(showInSubMenu); - } - - /** - * Deletes the selection. Equivalent to pressing the Delete key. - */ - void delete() { - mDeleteAction.run(); - } - - /** - * Add new root in an existing empty XML layout. - * <p/> - * In case of error (unknown FQCN, document not empty), silently do nothing. - * In case of success, the new element will have some default attributes set - * (xmlns:android, layout_width and height). The edit is wrapped in a proper - * undo. - * <p/> - * This is invoked by - * {@link MoveGesture#drop(org.eclipse.swt.dnd.DropTargetEvent)}. - * - * @param root A non-null descriptor of the root element to create. - */ - void createDocumentRoot(final @NonNull SimpleElement root) { - String rootFqcn = root.getFqcn(); - - // Need a valid empty document to create the new root - final UiDocumentNode uiDoc = mEditorDelegate.getUiRootNode(); - if (uiDoc == null || uiDoc.getUiChildren().size() > 0) { - debugPrintf("Failed to create document root for %1$s: document is not empty", - rootFqcn); - return; - } - - // Find the view descriptor matching our FQCN - final ViewElementDescriptor viewDesc = mEditorDelegate.getFqcnViewDescriptor(rootFqcn); - if (viewDesc == null) { - // TODO this could happen if dropping a custom view not known in this project - debugPrintf("Failed to add document root, unknown FQCN %1$s", rootFqcn); - return; - } - - // Get the last segment of the FQCN for the undo title - String title = rootFqcn; - int pos = title.lastIndexOf('.'); - if (pos > 0 && pos < title.length() - 1) { - title = title.substring(pos + 1); - } - title = String.format("Create root %1$s in document", title); - - mEditorDelegate.getEditor().wrapUndoEditXmlModel(title, new Runnable() { - @Override - public void run() { - UiElementNode uiNew = uiDoc.appendNewUiChild(viewDesc); - - // A root node requires the Android XMLNS - uiNew.setAttributeValue( - SdkConstants.ANDROID_NS_NAME, - SdkConstants.XMLNS_URI, - SdkConstants.NS_RESOURCES, - true /*override*/); - - IDragAttribute[] attributes = root.getAttributes(); - if (attributes != null) { - for (IDragAttribute attribute : attributes) { - String uri = attribute.getUri(); - String name = attribute.getName(); - String value = attribute.getValue(); - uiNew.setAttributeValue(name, uri, value, false /*override*/); - } - } - - // Adjust the attributes - DescriptorsUtils.setDefaultLayoutAttributes(uiNew, false /*updateLayout*/); - - uiNew.createXmlNode(); - } - }); - } - - /** - * Returns the insets associated with views of the given fully qualified name, for the - * current theme and screen type. - * - * @param fqcn the fully qualified name to the widget type - * @return the insets, or null if unknown - */ - public Margins getInsets(String fqcn) { - if (ViewMetadataRepository.INSETS_SUPPORTED) { - ConfigurationChooser configComposite = getGraphicalEditor().getConfigurationChooser(); - String theme = configComposite.getThemeName(); - Density density = configComposite.getConfiguration().getDensity(); - return ViewMetadataRepository.getInsets(fqcn, density, theme); - } else { - return null; - } - } - - private void debugPrintf(String message, Object... params) { - if (DEBUG) { - AdtPlugin.printToConsole("Canvas", String.format(message, params)); - } - } - - /** The associated editor has been deactivated */ - public void deactivated() { - // Force the tooltip to be hidden. If you switch from the layout editor - // to a Java editor with the keyboard, the tooltip can stay open. - if (mLintTooltipManager != null) { - mLintTooltipManager.hide(); - } - } - - /** @see #setPreview(RenderPreview) */ - private RenderPreview mPreview; - - /** - * Sets the {@link RenderPreview} associated with the currently rendering - * configuration. - * <p> - * A {@link RenderPreview} has various additional state beyond its rendering, - * such as its display name (which can be edited by the user). When you click on - * previews, the layout editor switches to show the given configuration preview. - * The preview is then no longer shown in the list of previews and is instead rendered - * in the main editor. However, when you then switch away to some other preview, we - * want to be able to restore the preview with all its state. - * - * @param preview the preview associated with the current canvas - */ - public void setPreview(@Nullable RenderPreview preview) { - mPreview = preview; - } - - /** - * Returns the {@link RenderPreview} associated with this layout canvas. - * - * @see #setPreview(RenderPreview) - * @return the {@link RenderPreview} - */ - @Nullable - public RenderPreview getPreview() { - return mPreview; - } - - /** Ensures that the configuration previews are up to date for this canvas */ - public void syncPreviewMode() { - if (mImageOverlay != null && mImageOverlay.getImage() != null && - getGraphicalEditor().getConfigurationChooser().getResources() != null) { - if (mPreviewManager.recomputePreviews(false)) { - // Zoom when syncing modes - mZoomFitNextImage = true; - ensureZoomed(); - } - } - } -} |