/* * Copyright (C) 2010 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.ide.common.api.DrawingStyle; import com.android.ide.common.api.IGraphics; import com.android.ide.common.api.INode; import com.android.ide.common.api.Margins; import com.android.ide.common.api.Rect; import com.android.ide.eclipse.adt.internal.editors.layout.gre.NodeProxy; import com.android.ide.eclipse.adt.internal.editors.layout.gre.RulesEngine; import org.eclipse.swt.graphics.GC; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; /** * The {@link SelectionOverlay} paints the current selection as an overlay. */ public class SelectionOverlay extends Overlay { private final LayoutCanvas mCanvas; private boolean mHidden; /** * Constructs a new {@link SelectionOverlay} tied to the given canvas. * * @param canvas the associated canvas */ public SelectionOverlay(LayoutCanvas canvas) { mCanvas = canvas; } /** * Set whether the selection overlay should be hidden. This is done during some * gestures like resize where the new bounds could be confused with the current * selection bounds. * * @param hidden when true, hide the selection bounds, when false, unhide. */ public void setHidden(boolean hidden) { mHidden = hidden; } /** * Paints the selection. * * @param selectionManager The {@link SelectionManager} holding the * selection. * @param gcWrapper The graphics context wrapper for the layout rules to use. * @param gc The SWT graphics object * @param rulesEngine The {@link RulesEngine} holding the rules. */ public void paint(SelectionManager selectionManager, GCWrapper gcWrapper, GC gc, RulesEngine rulesEngine) { if (mHidden) { return; } List<SelectionItem> selections = selectionManager.getSelections(); int n = selections.size(); if (n > 0) { List<NodeProxy> selectedNodes = new ArrayList<NodeProxy>(); boolean isMultipleSelection = n > 1; for (SelectionItem s : selections) { if (s.isRoot()) { // The root selection is never painted continue; } NodeProxy node = s.getNode(); if (node != null) { paintSelection(gcWrapper, gc, s, isMultipleSelection); selectedNodes.add(node); } } if (selectedNodes.size() > 0) { paintSelectionFeedback(gcWrapper, selectedNodes, rulesEngine); } else { CanvasViewInfo root = mCanvas.getViewHierarchy().getRoot(); if (root != null) { NodeProxy parent = mCanvas.getNodeFactory().create(root); rulesEngine.callPaintSelectionFeedback(gcWrapper, parent, Collections.<INode>emptyList(), root.getViewObject()); } } if (n == 1) { NodeProxy node = selections.get(0).getNode(); if (node != null) { paintHints(gcWrapper, node, rulesEngine); } } } else { CanvasViewInfo root = mCanvas.getViewHierarchy().getRoot(); if (root != null) { NodeProxy parent = mCanvas.getNodeFactory().create(root); rulesEngine.callPaintSelectionFeedback(gcWrapper, parent, Collections.<INode>emptyList(), root.getViewObject()); } } } /** Paint hint for current selection */ private void paintHints(GCWrapper gcWrapper, NodeProxy node, RulesEngine rulesEngine) { INode parent = node.getParent(); if (parent instanceof NodeProxy) { NodeProxy parentNode = (NodeProxy) parent; List<String> infos = rulesEngine.callGetSelectionHint(parentNode, node); if (infos != null && infos.size() > 0) { gcWrapper.useStyle(DrawingStyle.HELP); Rect b = mCanvas.getImageOverlay().getImageBounds(); if (b == null) { return; } // Compute the location to display the help. This is done in // layout coordinates, so we need to apply the scale in reverse // when making pixel margins // TODO: We could take the Canvas dimensions into account to see // where there is more room. // TODO: The scrollbars should take the presence of hint text // into account. double scale = mCanvas.getScale(); int x, y; if (b.w > b.h) { x = (int) (b.x + 3 / scale); y = (int) (b.y + b.h + 6 / scale); } else { x = (int) (b.x + b.w + 6 / scale); y = (int) (b.y + 3 / scale); } gcWrapper.drawBoxedStrings(x, y, infos); } } } private void paintSelectionFeedback(GCWrapper gcWrapper, List<NodeProxy> nodes, RulesEngine rulesEngine) { // Add fastpath for n=1 // Group nodes into parent/child groups Set<INode> parents = new HashSet<INode>(); for (INode node : nodes) { INode parent = node.getParent(); if (/*parent == null || */parent instanceof NodeProxy) { NodeProxy parentNode = (NodeProxy) parent; parents.add(parentNode); } } ViewHierarchy viewHierarchy = mCanvas.getViewHierarchy(); for (INode parent : parents) { List<INode> children = new ArrayList<INode>(); for (INode node : nodes) { INode nodeParent = node.getParent(); if (nodeParent == parent) { children.add(node); } } CanvasViewInfo viewInfo = viewHierarchy.findViewInfoFor((NodeProxy) parent); Object view = viewInfo != null ? viewInfo.getViewObject() : null; rulesEngine.callPaintSelectionFeedback(gcWrapper, (NodeProxy) parent, children, view); } } /** Called by the canvas when a view is being selected. */ private void paintSelection(IGraphics gc, GC swtGc, SelectionItem item, boolean isMultipleSelection) { CanvasViewInfo view = item.getViewInfo(); if (view.isHidden()) { return; } NodeProxy selectedNode = item.getNode(); Rect r = selectedNode.getBounds(); if (!r.isValid()) { return; } gc.useStyle(DrawingStyle.SELECTION); Margins insets = mCanvas.getInsets(selectedNode.getFqcn()); int x1 = r.x; int y1 = r.y; int x2 = r.x2() + 1; int y2 = r.y2() + 1; if (insets != null) { x1 += insets.left; x2 -= insets.right; y1 += insets.top; y2 -= insets.bottom; } gc.drawRect(x1, y1, x2, y2); // Paint sibling rectangles, if applicable List<CanvasViewInfo> siblings = view.getNodeSiblings(); if (siblings != null) { for (CanvasViewInfo sibling : siblings) { if (sibling != view) { r = SwtUtils.toRect(sibling.getSelectionRect()); gc.fillRect(r); gc.drawRect(r); } } } // Paint selection handles. These are painted in control coordinates on the // real SWT GC object rather than in layout coordinates on the GCWrapper, // since we want them to have a fixed size that is independent of the // screen zoom. CanvasTransform horizontalTransform = mCanvas.getHorizontalTransform(); CanvasTransform verticalTransform = mCanvas.getVerticalTransform(); int radius = SelectionHandle.PIXEL_RADIUS; int doubleRadius = 2 * radius; for (SelectionHandle handle : item.getSelectionHandles()) { int cx = horizontalTransform.translate(handle.centerX); int cy = verticalTransform.translate(handle.centerY); SwtDrawingStyle style = SwtDrawingStyle.of(DrawingStyle.SELECTION); gc.setAlpha(style.getStrokeAlpha()); swtGc.fillRectangle(cx - radius, cy - radius, doubleRadius, doubleRadius); } } }