diff options
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionManager.java')
-rw-r--r-- | eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionManager.java | 1262 |
1 files changed, 0 insertions, 1262 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionManager.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionManager.java deleted file mode 100644 index eb3d6f290..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionManager.java +++ /dev/null @@ -1,1262 +0,0 @@ -/* - * 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 static com.android.SdkConstants.ANDROID_URI; -import static com.android.SdkConstants.ATTR_ID; -import static com.android.SdkConstants.FQCN_SPACE; -import static com.android.SdkConstants.FQCN_SPACE_V7; -import static com.android.SdkConstants.NEW_ID_PREFIX; -import static com.android.ide.eclipse.adt.internal.editors.layout.gle2.SelectionHandle.PIXEL_MARGIN; -import static com.android.ide.eclipse.adt.internal.editors.layout.gle2.SelectionHandle.PIXEL_RADIUS; - -import com.android.SdkConstants; -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.ide.common.api.INode; -import com.android.ide.common.api.RuleAction; -import com.android.ide.common.layout.BaseViewRule; -import com.android.ide.common.layout.GridLayoutRule; -import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor; -import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate; -import com.android.ide.eclipse.adt.internal.editors.layout.gre.NodeFactory; -import com.android.ide.eclipse.adt.internal.editors.layout.gre.NodeProxy; -import com.android.ide.eclipse.adt.internal.editors.layout.gre.RulesEngine; -import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode; -import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode; -import com.android.ide.eclipse.adt.internal.refactorings.core.RenameResourceWizard; -import com.android.ide.eclipse.adt.internal.refactorings.core.RenameResult; -import com.android.ide.eclipse.adt.internal.resources.ResourceNameValidator; -import com.android.resources.ResourceType; -import com.android.utils.Pair; - -import org.eclipse.core.resources.IProject; -import org.eclipse.core.runtime.ListenerList; -import org.eclipse.jface.action.Action; -import org.eclipse.jface.action.ActionContributionItem; -import org.eclipse.jface.action.IAction; -import org.eclipse.jface.action.Separator; -import org.eclipse.jface.dialogs.InputDialog; -import org.eclipse.jface.util.SafeRunnable; -import org.eclipse.jface.viewers.ISelection; -import org.eclipse.jface.viewers.ISelectionChangedListener; -import org.eclipse.jface.viewers.ISelectionProvider; -import org.eclipse.jface.viewers.ITreeSelection; -import org.eclipse.jface.viewers.SelectionChangedEvent; -import org.eclipse.jface.viewers.TreePath; -import org.eclipse.jface.viewers.TreeSelection; -import org.eclipse.jface.window.Window; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.MenuDetectEvent; -import org.eclipse.swt.events.MouseEvent; -import org.eclipse.swt.widgets.Display; -import org.eclipse.swt.widgets.Menu; -import org.eclipse.ui.IWorkbenchPartSite; -import org.w3c.dom.Node; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.ListIterator; -import java.util.Set; - -/** - * The {@link SelectionManager} manages the selection in the canvas editor. - * It holds (and can be asked about) the set of selected items, and it also has - * operations for manipulating the selection - such as toggling items, copying - * the selection to the clipboard, etc. - * <p/> - * This class implements {@link ISelectionProvider} so that it can delegate - * the selection provider from the {@link LayoutCanvasViewer}. - * <p/> - * Note that {@link LayoutCanvasViewer} sets a selection change listener on this - * manager so that it can invoke its own fireSelectionChanged when the canvas' - * selection changes. - */ -public class SelectionManager implements ISelectionProvider { - - private LayoutCanvas mCanvas; - - /** The current selection list. The list is never null, however it can be empty. */ - private final LinkedList<SelectionItem> mSelections = new LinkedList<SelectionItem>(); - - /** An unmodifiable view of {@link #mSelections}. */ - private final List<SelectionItem> mUnmodifiableSelection = - Collections.unmodifiableList(mSelections); - - /** Barrier set when updating the selection to prevent from recursively - * invoking ourselves. */ - private boolean mInsideUpdateSelection; - - /** - * The <em>current</em> alternate selection, if any, which changes when the Alt key is - * used during a selection. Can be null. - */ - private CanvasAlternateSelection mAltSelection; - - /** List of clients listening to selection changes. */ - private final ListenerList mSelectionListeners = new ListenerList(); - - /** - * Constructs a new {@link SelectionManager} associated with the given layout canvas. - * - * @param layoutCanvas The layout canvas to create a {@link SelectionManager} for. - */ - public SelectionManager(LayoutCanvas layoutCanvas) { - mCanvas = layoutCanvas; - } - - @Override - public void addSelectionChangedListener(ISelectionChangedListener listener) { - mSelectionListeners.add(listener); - } - - @Override - public void removeSelectionChangedListener(ISelectionChangedListener listener) { - mSelectionListeners.remove(listener); - } - - /** - * Returns the native {@link SelectionItem} list. - * - * @return An immutable list of {@link SelectionItem}. Can be empty but not null. - */ - @NonNull - List<SelectionItem> getSelections() { - return mUnmodifiableSelection; - } - - /** - * Return a snapshot/copy of the selection. Useful for clipboards etc where we - * don't want the returned copy to be affected by future edits to the selection. - * - * @return A copy of the current selection. Never null. - */ - @NonNull - public List<SelectionItem> getSnapshot() { - if (mSelectionListeners.isEmpty()) { - return Collections.emptyList(); - } - - return new ArrayList<SelectionItem>(mSelections); - } - - /** - * Returns a {@link TreeSelection} where each {@link TreePath} item is - * actually a {@link CanvasViewInfo}. - */ - @Override - public ISelection getSelection() { - if (mSelections.isEmpty()) { - return TreeSelection.EMPTY; - } - - ArrayList<TreePath> paths = new ArrayList<TreePath>(); - - for (SelectionItem cs : mSelections) { - CanvasViewInfo vi = cs.getViewInfo(); - if (vi != null) { - paths.add(getTreePath(vi)); - } - } - - return new TreeSelection(paths.toArray(new TreePath[paths.size()])); - } - - /** - * Create a {@link TreePath} from the given view info - * - * @param viewInfo the view info to look up a tree path for - * @return a {@link TreePath} for the given view info - */ - public static TreePath getTreePath(CanvasViewInfo viewInfo) { - ArrayList<Object> segments = new ArrayList<Object>(); - while (viewInfo != null) { - segments.add(0, viewInfo); - viewInfo = viewInfo.getParent(); - } - - return new TreePath(segments.toArray()); - } - - /** - * Sets the selection. It must be an {@link ITreeSelection} where each segment - * of the tree path is a {@link CanvasViewInfo}. A null selection is considered - * as an empty selection. - * <p/> - * This method is invoked by {@link LayoutCanvasViewer#setSelection(ISelection)} - * in response to an <em>outside</em> selection (compatible with ours) that has - * changed. Typically it means the outline selection has changed and we're - * synchronizing ours to match. - */ - @Override - public void setSelection(ISelection selection) { - if (mInsideUpdateSelection) { - return; - } - - boolean changed = false; - try { - mInsideUpdateSelection = true; - - if (selection == null) { - selection = TreeSelection.EMPTY; - } - - if (selection instanceof ITreeSelection) { - ITreeSelection treeSel = (ITreeSelection) selection; - - if (treeSel.isEmpty()) { - // Clear existing selection, if any - if (!mSelections.isEmpty()) { - mSelections.clear(); - mAltSelection = null; - updateActionsFromSelection(); - redraw(); - } - return; - } - - boolean redoLayout = false; - - // Create a list of all currently selected view infos - Set<CanvasViewInfo> oldSelected = new HashSet<CanvasViewInfo>(); - for (SelectionItem cs : mSelections) { - oldSelected.add(cs.getViewInfo()); - } - - // Go thru new selection and take care of selecting new items - // or marking those which are the same as in the current selection - for (TreePath path : treeSel.getPaths()) { - Object seg = path.getLastSegment(); - if (seg instanceof CanvasViewInfo) { - CanvasViewInfo newVi = (CanvasViewInfo) seg; - if (oldSelected.contains(newVi)) { - // This view info is already selected. Remove it from the - // oldSelected list so that we don't deselect it later. - oldSelected.remove(newVi); - } else { - // This view info is not already selected. Select it now. - - // reset alternate selection if any - mAltSelection = null; - // otherwise add it. - mSelections.add(createSelection(newVi)); - changed = true; - } - if (newVi.isInvisible()) { - redoLayout = true; - } - } else { - // Unrelated selection (e.g. user clicked in the Project Explorer - // or something) -- just ignore these - return; - } - } - - // Deselect old selected items that are not in the new one - for (CanvasViewInfo vi : oldSelected) { - if (vi.isExploded()) { - redoLayout = true; - } - deselect(vi); - changed = true; - } - - if (redoLayout) { - mCanvas.getEditorDelegate().recomputeLayout(); - } - } - } finally { - mInsideUpdateSelection = false; - } - - if (changed) { - redraw(); - fireSelectionChanged(); - updateActionsFromSelection(); - } - } - - /** - * The menu has been activated; ensure that the menu click is over the existing - * selection, and if not, update the selection. - * - * @param e the {@link MenuDetectEvent} which triggered the menu - */ - public void menuClick(MenuDetectEvent e) { - LayoutPoint p = ControlPoint.create(mCanvas, e).toLayout(); - - // Right click button is used to display a context menu. - // If there's an existing selection and the click is anywhere in this selection - // and there are no modifiers being used, we don't want to change the selection. - // Otherwise we select the item under the cursor. - - for (SelectionItem cs : mSelections) { - if (cs.isRoot()) { - continue; - } - if (cs.getRect().contains(p.x, p.y)) { - // The cursor is inside the selection. Don't change anything. - return; - } - } - - CanvasViewInfo vi = mCanvas.getViewHierarchy().findViewInfoAt(p); - selectSingle(vi); - } - - /** - * Performs selection for a mouse event. - * <p/> - * Shift key (or Command on the Mac) is used to toggle in multi-selection. - * Alt key is used to cycle selection through objects at the same level than - * the one pointed at (i.e. click on an object then alt-click to cycle). - * - * @param e The mouse event which triggered the selection. Cannot be null. - * The modifier key mask will be used to determine whether this - * is a plain select or a toggle, etc. - */ - public void select(MouseEvent e) { - boolean isMultiClick = (e.stateMask & SWT.SHIFT) != 0 || - // On Mac, the Command key is the normal toggle accelerator - ((SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_DARWIN) && - (e.stateMask & SWT.COMMAND) != 0); - boolean isCycleClick = (e.stateMask & SWT.ALT) != 0; - - LayoutPoint p = ControlPoint.create(mCanvas, e).toLayout(); - - if (e.button == 3) { - // Right click button is used to display a context menu. - // If there's an existing selection and the click is anywhere in this selection - // and there are no modifiers being used, we don't want to change the selection. - // Otherwise we select the item under the cursor. - - if (!isCycleClick && !isMultiClick) { - for (SelectionItem cs : mSelections) { - if (cs.getRect().contains(p.x, p.y)) { - // The cursor is inside the selection. Don't change anything. - return; - } - } - } - - } else if (e.button != 1) { - // Click was done with something else than the left button for normal selection - // or the right button for context menu. - // We don't use mouse button 2 yet (middle mouse, or scroll wheel?) for - // anything, so let's not change the selection. - return; - } - - CanvasViewInfo vi = mCanvas.getViewHierarchy().findViewInfoAt(p); - - if (vi != null && vi.isHidden()) { - vi = vi.getParent(); - } - - if (isMultiClick && !isCycleClick) { - // Case where shift is pressed: pointed object is toggled. - - // reset alternate selection if any - mAltSelection = null; - - // If nothing has been found at the cursor, assume it might be a user error - // and avoid clearing the existing selection. - - if (vi != null) { - // toggle this selection on-off: remove it if already selected - if (deselect(vi)) { - if (vi.isExploded()) { - mCanvas.getEditorDelegate().recomputeLayout(); - } - - redraw(); - return; - } - - // otherwise add it. - mSelections.add(createSelection(vi)); - fireSelectionChanged(); - redraw(); - } - - } else if (isCycleClick) { - // Case where alt is pressed: select or cycle the object pointed at. - - // Note: if shift and alt are pressed, shift is ignored. The alternate selection - // mechanism does not reset the current multiple selection unless they intersect. - - // We need to remember the "origin" of the alternate selection, to be - // able to continue cycling through it later. If there's no alternate selection, - // create one. If there's one but not for the same origin object, create a new - // one too. - if (mAltSelection == null || mAltSelection.getOriginatingView() != vi) { - mAltSelection = new CanvasAlternateSelection( - vi, mCanvas.getViewHierarchy().findAltViewInfoAt(p)); - - // deselect them all, in case they were partially selected - deselectAll(mAltSelection.getAltViews()); - - // select the current one - CanvasViewInfo vi2 = mAltSelection.getCurrent(); - if (vi2 != null) { - mSelections.addFirst(createSelection(vi2)); - fireSelectionChanged(); - } - } else { - // We're trying to cycle through the current alternate selection. - // First remove the current object. - CanvasViewInfo vi2 = mAltSelection.getCurrent(); - deselect(vi2); - - // Now select the next one. - vi2 = mAltSelection.getNext(); - if (vi2 != null) { - mSelections.addFirst(createSelection(vi2)); - fireSelectionChanged(); - } - } - redraw(); - - } else { - // Case where no modifier is pressed: either select or reset the selection. - selectSingle(vi); - } - } - - /** - * Removes all the currently selected item and only select the given item. - * Issues a redraw() if the selection changes. - * - * @param vi The new selected item if non-null. Selection becomes empty if null. - * @return the item selected, or null if the selection was cleared (e.g. vi was null) - */ - @Nullable - SelectionItem selectSingle(CanvasViewInfo vi) { - SelectionItem item = null; - - // reset alternate selection if any - mAltSelection = null; - - if (vi == null) { - // The user clicked outside the bounds of the root element; in that case, just - // select the root element. - vi = mCanvas.getViewHierarchy().getRoot(); - } - - boolean redoLayout = hasExplodedItems(); - - // reset (multi)selection if any - if (!mSelections.isEmpty()) { - if (mSelections.size() == 1 && mSelections.getFirst().getViewInfo() == vi) { - // CanvasSelection remains the same, don't touch it. - return mSelections.getFirst(); - } - mSelections.clear(); - } - - if (vi != null) { - item = createSelection(vi); - mSelections.add(item); - if (vi.isInvisible()) { - redoLayout = true; - } - } - fireSelectionChanged(); - - if (redoLayout) { - mCanvas.getEditorDelegate().recomputeLayout(); - } - - redraw(); - - return item; - } - - /** Returns true if the view hierarchy is showing exploded items. */ - private boolean hasExplodedItems() { - for (SelectionItem item : mSelections) { - if (item.getViewInfo().isExploded()) { - return true; - } - } - - return false; - } - - /** - * Selects the given set of {@link CanvasViewInfo}s. This is similar to - * {@link #selectSingle} but allows you to make a multi-selection. Issues a - * {@link #redraw()}. - * - * @param viewInfos A collection of {@link CanvasViewInfo} objects to be - * selected, or null or empty to clear the selection. - */ - /* package */ void selectMultiple(Collection<CanvasViewInfo> viewInfos) { - // reset alternate selection if any - mAltSelection = null; - - boolean redoLayout = hasExplodedItems(); - - mSelections.clear(); - if (viewInfos != null) { - for (CanvasViewInfo viewInfo : viewInfos) { - mSelections.add(createSelection(viewInfo)); - if (viewInfo.isInvisible()) { - redoLayout = true; - } - } - } - - fireSelectionChanged(); - - if (redoLayout) { - mCanvas.getEditorDelegate().recomputeLayout(); - } - - redraw(); - } - - public void select(Collection<INode> nodes) { - List<CanvasViewInfo> infos = new ArrayList<CanvasViewInfo>(nodes.size()); - for (INode node : nodes) { - CanvasViewInfo info = mCanvas.getViewHierarchy().findViewInfoFor(node); - if (info != null) { - infos.add(info); - } - } - selectMultiple(infos); - } - - /** - * Selects the visual element corresponding to the given XML node - * @param xmlNode The Node whose element we want to select. - */ - /* package */ void select(Node xmlNode) { - if (xmlNode == null) { - return; - } else if (xmlNode.getNodeType() == Node.TEXT_NODE) { - xmlNode = xmlNode.getParentNode(); - } - - CanvasViewInfo vi = mCanvas.getViewHierarchy().findViewInfoFor(xmlNode); - if (vi != null && !vi.isRoot()) { - selectSingle(vi); - } - } - - /** - * Selects any views that overlap the given selection rectangle. - * - * @param topLeft The top left corner defining the selection rectangle. - * @param bottomRight The bottom right corner defining the selection - * rectangle. - * @param toggled A set of {@link CanvasViewInfo}s that should be toggled - * rather than just added. - */ - public void selectWithin(LayoutPoint topLeft, LayoutPoint bottomRight, - Collection<CanvasViewInfo> toggled) { - // reset alternate selection if any - mAltSelection = null; - - ViewHierarchy viewHierarchy = mCanvas.getViewHierarchy(); - Collection<CanvasViewInfo> viewInfos = viewHierarchy.findWithin(topLeft, bottomRight); - - if (toggled.size() > 0) { - // Copy; we're not allowed to touch the passed in collection - Set<CanvasViewInfo> result = new HashSet<CanvasViewInfo>(toggled); - for (CanvasViewInfo viewInfo : viewInfos) { - if (toggled.contains(viewInfo)) { - result.remove(viewInfo); - } else { - result.add(viewInfo); - } - } - viewInfos = result; - } - - mSelections.clear(); - for (CanvasViewInfo viewInfo : viewInfos) { - if (viewInfo.isHidden()) { - continue; - } - mSelections.add(createSelection(viewInfo)); - } - - fireSelectionChanged(); - redraw(); - } - - /** - * Clears the selection and then selects everything (all views and all their - * children). - */ - public void selectAll() { - // First clear the current selection, if any. - mSelections.clear(); - mAltSelection = null; - - // Now select everything if there's a valid layout - for (CanvasViewInfo vi : mCanvas.getViewHierarchy().findAllViewInfos(false)) { - mSelections.add(createSelection(vi)); - } - - fireSelectionChanged(); - redraw(); - } - - /** Clears the selection */ - public void selectNone() { - mSelections.clear(); - mAltSelection = null; - fireSelectionChanged(); - redraw(); - } - - /** Selects the parent of the current selection */ - public void selectParent() { - if (mSelections.size() == 1) { - CanvasViewInfo parent = mSelections.get(0).getViewInfo().getParent(); - if (parent != null) { - selectSingle(parent); - } - } - } - - /** Finds all widgets in the layout that have the same type as the primary */ - public void selectSameType() { - // Find all - if (mSelections.size() == 1) { - CanvasViewInfo viewInfo = mSelections.get(0).getViewInfo(); - ElementDescriptor descriptor = viewInfo.getUiViewNode().getDescriptor(); - mSelections.clear(); - mAltSelection = null; - addSameType(mCanvas.getViewHierarchy().getRoot(), descriptor); - fireSelectionChanged(); - redraw(); - } - } - - /** Helper for {@link #selectSameType} */ - private void addSameType(CanvasViewInfo root, ElementDescriptor descriptor) { - if (root.getUiViewNode().getDescriptor() == descriptor) { - mSelections.add(createSelection(root)); - } - - for (CanvasViewInfo child : root.getChildren()) { - addSameType(child, descriptor); - } - } - - /** Selects the siblings of the primary */ - public void selectSiblings() { - // Find all - if (mSelections.size() == 1) { - CanvasViewInfo vi = mSelections.get(0).getViewInfo(); - mSelections.clear(); - mAltSelection = null; - CanvasViewInfo parent = vi.getParent(); - if (parent == null) { - selectNone(); - } else { - for (CanvasViewInfo child : parent.getChildren()) { - mSelections.add(createSelection(child)); - } - fireSelectionChanged(); - redraw(); - } - } - } - - /** - * Returns true if and only if there is currently more than one selected - * item. - * - * @return True if more than one item is selected - */ - public boolean hasMultiSelection() { - return mSelections.size() > 1; - } - - /** - * Deselects a view info. Returns true if the object was actually selected. - * Callers are responsible for calling redraw() and updateOulineSelection() - * after. - * @param canvasViewInfo The item to deselect. - * @return True if the object was successfully removed from the selection. - */ - public boolean deselect(CanvasViewInfo canvasViewInfo) { - if (canvasViewInfo == null) { - return false; - } - - for (ListIterator<SelectionItem> it = mSelections.listIterator(); it.hasNext(); ) { - SelectionItem s = it.next(); - if (canvasViewInfo == s.getViewInfo()) { - it.remove(); - return true; - } - } - - return false; - } - - /** - * Deselects multiple view infos. - * Callers are responsible for calling redraw() and updateOulineSelection() after. - */ - private void deselectAll(List<CanvasViewInfo> canvasViewInfos) { - for (ListIterator<SelectionItem> it = mSelections.listIterator(); it.hasNext(); ) { - SelectionItem s = it.next(); - if (canvasViewInfos.contains(s.getViewInfo())) { - it.remove(); - } - } - } - - /** Sync the selection with an updated view info tree */ - void sync() { - // Check if the selection is still the same (based on the object keys) - // and eventually recompute their bounds. - for (ListIterator<SelectionItem> it = mSelections.listIterator(); it.hasNext(); ) { - SelectionItem s = it.next(); - - // Check if the selected object still exists - ViewHierarchy viewHierarchy = mCanvas.getViewHierarchy(); - UiViewElementNode key = s.getViewInfo().getUiViewNode(); - CanvasViewInfo vi = viewHierarchy.findViewInfoFor(key); - - // Remove the previous selection -- if the selected object still exists - // we need to recompute its bounds in case it moved so we'll insert a new one - // at the same place. - it.remove(); - if (vi == null) { - vi = findCorresponding(s.getViewInfo(), viewHierarchy.getRoot()); - } - if (vi != null) { - it.add(createSelection(vi)); - } - } - fireSelectionChanged(); - - // remove the current alternate selection views - mAltSelection = null; - } - - /** Finds the corresponding {@link CanvasViewInfo} in the new hierarchy */ - private CanvasViewInfo findCorresponding(CanvasViewInfo old, CanvasViewInfo newRoot) { - CanvasViewInfo oldParent = old.getParent(); - if (oldParent != null) { - CanvasViewInfo newParent = findCorresponding(oldParent, newRoot); - if (newParent == null) { - return null; - } - - List<CanvasViewInfo> oldSiblings = oldParent.getChildren(); - List<CanvasViewInfo> newSiblings = newParent.getChildren(); - Iterator<CanvasViewInfo> oldIterator = oldSiblings.iterator(); - Iterator<CanvasViewInfo> newIterator = newSiblings.iterator(); - while (oldIterator.hasNext() && newIterator.hasNext()) { - CanvasViewInfo oldSibling = oldIterator.next(); - CanvasViewInfo newSibling = newIterator.next(); - - if (oldSibling.getName().equals(newSibling.getName())) { - // Structure has changed: can't do a proper search - return null; - } - - if (oldSibling == old) { - return newSibling; - } - } - } else { - return newRoot; - } - - return null; - } - - /** - * Notifies listeners that the selection has changed. - */ - private void fireSelectionChanged() { - if (mInsideUpdateSelection) { - return; - } - try { - mInsideUpdateSelection = true; - - final SelectionChangedEvent event = new SelectionChangedEvent(this, getSelection()); - - SafeRunnable.run(new SafeRunnable() { - @Override - public void run() { - for (Object listener : mSelectionListeners.getListeners()) { - ((ISelectionChangedListener) listener).selectionChanged(event); - } - } - }); - - updateActionsFromSelection(); - } finally { - mInsideUpdateSelection = false; - } - } - - /** - * Updates menu actions and the layout action bar after a selection change - these are - * actions that depend on the selection - */ - private void updateActionsFromSelection() { - LayoutEditorDelegate editor = mCanvas.getEditorDelegate(); - if (editor != null) { - // Update menu actions that depend on the selection - mCanvas.updateMenuActionState(); - - // Update the layout actions bar - LayoutActionBar layoutActionBar = editor.getGraphicalEditor().getLayoutActionBar(); - layoutActionBar.updateSelection(); - } - } - - /** - * Sanitizes the selection for a copy/cut or drag operation. - * <p/> - * Sanitizes the list to make sure all elements have a valid XML attached to it, - * that is remove element that have no XML to avoid having to make repeated such - * checks in various places after. - * <p/> - * In case of multiple selection, we also need to remove all children when their - * parent is already selected since parents will always be added with all their - * children. - * <p/> - * - * @param selection The selection list to be sanitized <b>in-place</b>. - * The <code>selection</code> argument should not be {@link #mSelections} -- the - * given list is going to be altered and we should never alter the user-made selection. - * Instead the caller should provide its own copy. - */ - /* package */ static void sanitize(List<SelectionItem> selection) { - if (selection.isEmpty()) { - return; - } - - for (Iterator<SelectionItem> it = selection.iterator(); it.hasNext(); ) { - SelectionItem cs = it.next(); - CanvasViewInfo vi = cs.getViewInfo(); - UiViewElementNode key = vi == null ? null : vi.getUiViewNode(); - Node node = key == null ? null : key.getXmlNode(); - if (node == null) { - // Missing ViewInfo or view key or XML, discard this. - it.remove(); - continue; - } - - if (vi != null) { - for (Iterator<SelectionItem> it2 = selection.iterator(); - it2.hasNext(); ) { - SelectionItem cs2 = it2.next(); - if (cs != cs2) { - CanvasViewInfo vi2 = cs2.getViewInfo(); - if (vi.isParent(vi2)) { - // vi2 is a parent for vi. Remove vi. - it.remove(); - break; - } - } - } - } - } - } - - /** - * Selects the given list of nodes in the canvas, and returns true iff the - * attempt to select was successful. - * - * @param nodes The collection of nodes to be selected - * @param indices A list of indices within the parent for each node, or null - * @return True if and only if all nodes were successfully selected - */ - public boolean selectDropped(List<INode> nodes, List<Integer> indices) { - assert indices == null || nodes.size() == indices.size(); - - ViewHierarchy viewHierarchy = mCanvas.getViewHierarchy(); - - // Look up a list of view infos which correspond to the nodes. - final Collection<CanvasViewInfo> newChildren = new ArrayList<CanvasViewInfo>(); - for (int i = 0, n = nodes.size(); i < n; i++) { - INode node = nodes.get(i); - - CanvasViewInfo viewInfo = viewHierarchy.findViewInfoFor(node); - - // There are two scenarios where looking up a view info fails. - // The first one is that the node was just added and the render has not yet - // happened, so the ViewHierarchy has no record of the node. In this case - // there is nothing we can do, and the method will return false (which the - // caller will use to schedule a second attempt later). - // The second scenario is where the nodes *change identity*. This isn't - // common, but when a drop handler makes a lot of changes to its children, - // for example when dropping into a GridLayout where attributes are adjusted - // on nearly all the other children to update row or column attributes - // etc, then in some cases Eclipse's DOM model changes the identities of - // the nodes when applying all the edits, so the new Node we created (as - // well as possibly other nodes) are no longer the children we observe - // after the edit, and there are new copies there instead. In this case - // the UiViewModel also fails to map the nodes. To work around this, - // we track the *indices* (within the parent) during a drop, such that we - // know which children (according to their positions) the given nodes - // are supposed to map to, and then we use these view infos instead. - if (viewInfo == null && node instanceof NodeProxy && indices != null) { - INode parent = node.getParent(); - CanvasViewInfo parentViewInfo = viewHierarchy.findViewInfoFor(parent); - if (parentViewInfo != null) { - UiViewElementNode parentUiNode = parentViewInfo.getUiViewNode(); - if (parentUiNode != null) { - List<UiElementNode> children = parentUiNode.getUiChildren(); - int index = indices.get(i); - if (index >= 0 && index < children.size()) { - UiElementNode replacedNode = children.get(index); - viewInfo = viewHierarchy.findViewInfoFor(replacedNode); - } - } - } - } - - if (viewInfo != null) { - if (nodes.size() > 1 && viewInfo.isHidden()) { - // Skip spacers - unless you're dropping just one - continue; - } - if (GridLayoutRule.sDebugGridLayout && (viewInfo.getName().equals(FQCN_SPACE) - || viewInfo.getName().equals(FQCN_SPACE_V7))) { - // In debug mode they might not be marked as hidden but we never never - // want to select these guys - continue; - } - newChildren.add(viewInfo); - } - } - boolean found = nodes.size() == newChildren.size(); - - if (found || newChildren.size() > 0) { - mCanvas.getSelectionManager().selectMultiple(newChildren); - } - - return found; - } - - /** - * Update the outline selection to select the given nodes, asynchronously. - * @param nodes The nodes to be selected - */ - public void setOutlineSelection(final List<INode> nodes) { - Display.getDefault().asyncExec(new Runnable() { - @Override - public void run() { - selectDropped(nodes, null /* indices */); - syncOutlineSelection(); - } - }); - } - - /** - * Syncs the current selection to the outline, synchronously. - */ - public void syncOutlineSelection() { - OutlinePage outlinePage = mCanvas.getOutlinePage(); - IWorkbenchPartSite site = outlinePage.getEditor().getSite(); - ISelectionProvider selectionProvider = site.getSelectionProvider(); - ISelection selection = selectionProvider.getSelection(); - if (selection != null) { - outlinePage.setSelection(selection); - } - } - - private void redraw() { - mCanvas.redraw(); - } - - SelectionItem createSelection(CanvasViewInfo vi) { - return new SelectionItem(mCanvas, vi); - } - - /** - * Returns true if there is nothing selected - * - * @return true if there is nothing selected - */ - public boolean isEmpty() { - return mSelections.size() == 0; - } - - /** - * "Select" context menu which lists various menu options related to selection: - * <ul> - * <li> Select All - * <li> Select Parent - * <li> Select None - * <li> Select Siblings - * <li> Select Same Type - * </ul> - * etc. - */ - public static class SelectionMenu extends SubmenuAction { - private final GraphicalEditorPart mEditor; - - public SelectionMenu(GraphicalEditorPart editor) { - super("Select"); - mEditor = editor; - } - - @Override - public String getId() { - return "-selectionmenu"; //$NON-NLS-1$ - } - - @Override - protected void addMenuItems(Menu menu) { - LayoutCanvas canvas = mEditor.getCanvasControl(); - SelectionManager selectionManager = canvas.getSelectionManager(); - List<SelectionItem> selections = selectionManager.getSelections(); - boolean selectedOne = selections.size() == 1; - boolean notRoot = selectedOne && !selections.get(0).isRoot(); - boolean haveSelection = selections.size() > 0; - - Action a; - a = selectionManager.new SelectAction("Select Parent\tEsc", SELECT_PARENT); - new ActionContributionItem(a).fill(menu, -1); - a.setEnabled(notRoot); - a.setAccelerator(SWT.ESC); - - a = selectionManager.new SelectAction("Select Siblings", SELECT_SIBLINGS); - new ActionContributionItem(a).fill(menu, -1); - a.setEnabled(notRoot); - - a = selectionManager.new SelectAction("Select Same Type", SELECT_SAME_TYPE); - new ActionContributionItem(a).fill(menu, -1); - a.setEnabled(selectedOne); - - new Separator().fill(menu, -1); - - // Special case for Select All: Use global action - a = canvas.getSelectAllAction(); - new ActionContributionItem(a).fill(menu, -1); - a.setEnabled(true); - - a = selectionManager.new SelectAction("Deselect All", SELECT_NONE); - new ActionContributionItem(a).fill(menu, -1); - a.setEnabled(haveSelection); - } - } - - private static final int SELECT_PARENT = 1; - private static final int SELECT_SIBLINGS = 2; - private static final int SELECT_SAME_TYPE = 3; - private static final int SELECT_NONE = 4; // SELECT_ALL is handled separately - - private class SelectAction extends Action { - private final int mType; - - public SelectAction(String title, int type) { - super(title, IAction.AS_PUSH_BUTTON); - mType = type; - } - - @Override - public void run() { - switch (mType) { - case SELECT_NONE: - selectNone(); - break; - case SELECT_PARENT: - selectParent(); - break; - case SELECT_SAME_TYPE: - selectSameType(); - break; - case SELECT_SIBLINGS: - selectSiblings(); - break; - } - - List<INode> nodes = new ArrayList<INode>(); - for (SelectionItem item : getSelections()) { - nodes.add(item.getNode()); - } - setOutlineSelection(nodes); - } - } - - public Pair<SelectionItem, SelectionHandle> findHandle(ControlPoint controlPoint) { - if (!isEmpty()) { - LayoutPoint layoutPoint = controlPoint.toLayout(); - int distance = (int) ((PIXEL_MARGIN + PIXEL_RADIUS) / mCanvas.getScale()); - - for (SelectionItem item : getSelections()) { - SelectionHandles handles = item.getSelectionHandles(); - // See if it's over the selection handles - SelectionHandle handle = handles.findHandle(layoutPoint, distance); - if (handle != null) { - return Pair.of(item, handle); - } - } - - } - return null; - } - - /** Performs the default action provided by the currently selected view */ - public void performDefaultAction() { - final List<SelectionItem> selections = getSelections(); - if (selections.size() > 0) { - NodeProxy primary = selections.get(0).getNode(); - if (primary != null) { - RulesEngine rulesEngine = mCanvas.getRulesEngine(); - final String id = rulesEngine.callGetDefaultActionId(primary); - if (id == null) { - return; - } - final List<RuleAction> actions = rulesEngine.callGetContextMenu(primary); - if (actions == null) { - return; - } - RuleAction matching = null; - for (RuleAction a : actions) { - if (id.equals(a.getId())) { - matching = a; - break; - } - } - if (matching == null) { - return; - } - final List<INode> selectedNodes = new ArrayList<INode>(); - for (SelectionItem item : selections) { - NodeProxy n = item.getNode(); - if (n != null) { - selectedNodes.add(n); - } - } - final RuleAction action = matching; - mCanvas.getEditorDelegate().getEditor().wrapUndoEditXmlModel(action.getTitle(), - new Runnable() { - @Override - public void run() { - action.getCallback().action(action, selectedNodes, - action.getId(), null); - LayoutCanvas canvas = mCanvas; - CanvasViewInfo root = canvas.getViewHierarchy().getRoot(); - if (root != null) { - UiViewElementNode uiViewNode = root.getUiViewNode(); - NodeFactory nodeFactory = canvas.getNodeFactory(); - NodeProxy rootNode = nodeFactory.create(uiViewNode); - if (rootNode != null) { - rootNode.applyPendingChanges(); - } - } - } - }); - } - } - } - - /** Performs renaming the selected views */ - public void performRename() { - final List<SelectionItem> selections = getSelections(); - if (selections.size() > 0) { - NodeProxy primary = selections.get(0).getNode(); - if (primary != null) { - performRename(primary, selections); - } - } - } - - /** - * Performs renaming the given node. - * - * @param primary the node to be renamed, or the primary node (to get the - * current value from if more than one node should be renamed) - * @param selections if not null, a list of nodes to apply the setting to - * (which should include the primary) - * @return the result of the renaming operation - */ - @NonNull - public RenameResult performRename( - final @NonNull INode primary, - final @Nullable List<SelectionItem> selections) { - String id = primary.getStringAttr(ANDROID_URI, ATTR_ID); - if (id != null && !id.isEmpty()) { - RenameResult result = RenameResourceWizard.renameResource( - mCanvas.getShell(), - mCanvas.getEditorDelegate().getGraphicalEditor().getProject(), - ResourceType.ID, BaseViewRule.stripIdPrefix(id), null, true /*canClear*/); - if (result.isCanceled()) { - return result; - } else if (!result.isUnavailable()) { - return result; - } - } - String currentId = primary.getStringAttr(ANDROID_URI, ATTR_ID); - currentId = BaseViewRule.stripIdPrefix(currentId); - InputDialog d = new InputDialog( - AdtPlugin.getDisplay().getActiveShell(), - "Set ID", - "New ID:", - currentId, - ResourceNameValidator.create(false, (IProject) null, ResourceType.ID)); - if (d.open() == Window.OK) { - final String s = d.getValue(); - mCanvas.getEditorDelegate().getEditor().wrapUndoEditXmlModel("Set ID", - new Runnable() { - @Override - public void run() { - String newId = s; - newId = NEW_ID_PREFIX + BaseViewRule.stripIdPrefix(s); - if (selections != null) { - for (SelectionItem item : selections) { - NodeProxy node = item.getNode(); - if (node != null) { - node.setAttribute(ANDROID_URI, ATTR_ID, newId); - } - } - } else { - primary.setAttribute(ANDROID_URI, ATTR_ID, newId); - } - - LayoutCanvas canvas = mCanvas; - CanvasViewInfo root = canvas.getViewHierarchy().getRoot(); - if (root != null) { - UiViewElementNode uiViewNode = root.getUiViewNode(); - NodeFactory nodeFactory = canvas.getNodeFactory(); - NodeProxy rootNode = nodeFactory.create(uiViewNode); - if (rootNode != null) { - rootNode.applyPendingChanges(); - } - } - } - }); - return RenameResult.name(BaseViewRule.stripIdPrefix(s)); - } else { - return RenameResult.canceled(); - } - } -} |