Home | History | Annotate | Download | only in gle2
      1 /*
      2  * Copyright (C) 2010 The Android Open Source Project
      3  *
      4  * Licensed under the Eclipse Public License, Version 1.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.eclipse.org/org/documents/epl-v10.php
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.ide.eclipse.adt.internal.editors.layout.gle2;
     18 
     19 import static com.android.ide.common.layout.LayoutConstants.ANDROID_URI;
     20 import static com.android.ide.common.layout.LayoutConstants.ATTR_CLASS;
     21 import static com.android.ide.common.layout.LayoutConstants.ATTR_COLUMN_COUNT;
     22 import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_COLUMN;
     23 import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_COLUMN_SPAN;
     24 import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_ROW;
     25 import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_ROW_SPAN;
     26 import static com.android.ide.common.layout.LayoutConstants.ATTR_ORIENTATION;
     27 import static com.android.ide.common.layout.LayoutConstants.ATTR_ROW_COUNT;
     28 import static com.android.ide.common.layout.LayoutConstants.ATTR_SRC;
     29 import static com.android.ide.common.layout.LayoutConstants.ATTR_TEXT;
     30 import static com.android.ide.common.layout.LayoutConstants.DRAWABLE_PREFIX;
     31 import static com.android.ide.common.layout.LayoutConstants.GRID_LAYOUT;
     32 import static com.android.ide.common.layout.LayoutConstants.LAYOUT_PREFIX;
     33 import static com.android.ide.common.layout.LayoutConstants.LINEAR_LAYOUT;
     34 import static com.android.ide.common.layout.LayoutConstants.VALUE_VERTICAL;
     35 import static com.android.ide.eclipse.adt.internal.editors.layout.descriptors.LayoutDescriptors.VIEW_VIEWTAG;
     36 import static com.android.tools.lint.detector.api.LintConstants.AUTO_URI;
     37 import static com.android.tools.lint.detector.api.LintConstants.URI_PREFIX;
     38 import static org.eclipse.jface.viewers.StyledString.QUALIFIER_STYLER;
     39 
     40 import com.android.annotations.VisibleForTesting;
     41 import com.android.ide.common.api.INode;
     42 import com.android.ide.common.api.InsertType;
     43 import com.android.ide.common.layout.BaseLayoutRule;
     44 import com.android.ide.common.layout.GridLayoutRule;
     45 import com.android.ide.eclipse.adt.AdtPlugin;
     46 import com.android.ide.eclipse.adt.internal.editors.IconFactory;
     47 import com.android.ide.eclipse.adt.internal.editors.descriptors.DescriptorsUtils;
     48 import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor;
     49 import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate;
     50 import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.LayoutDescriptors;
     51 import com.android.ide.eclipse.adt.internal.editors.layout.gle2.IncludeFinder.Reference;
     52 import com.android.ide.eclipse.adt.internal.editors.layout.gre.NodeProxy;
     53 import com.android.ide.eclipse.adt.internal.editors.layout.properties.PropertySheetPage;
     54 import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode;
     55 import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestInfo;
     56 import com.android.ide.eclipse.adt.internal.editors.ui.ErrorImageComposite;
     57 import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
     58 import com.android.ide.eclipse.adt.internal.sdk.ProjectState;
     59 import com.android.ide.eclipse.adt.internal.sdk.Sdk;
     60 import com.android.util.Pair;
     61 
     62 import org.eclipse.core.resources.IProject;
     63 import org.eclipse.jface.action.Action;
     64 import org.eclipse.jface.action.ActionContributionItem;
     65 import org.eclipse.jface.action.IAction;
     66 import org.eclipse.jface.action.IContributionItem;
     67 import org.eclipse.jface.action.IMenuListener;
     68 import org.eclipse.jface.action.IMenuManager;
     69 import org.eclipse.jface.action.IToolBarManager;
     70 import org.eclipse.jface.action.MenuManager;
     71 import org.eclipse.jface.action.Separator;
     72 import org.eclipse.jface.preference.JFacePreferences;
     73 import org.eclipse.jface.viewers.DoubleClickEvent;
     74 import org.eclipse.jface.viewers.IDoubleClickListener;
     75 import org.eclipse.jface.viewers.IElementComparer;
     76 import org.eclipse.jface.viewers.ISelection;
     77 import org.eclipse.jface.viewers.ITreeContentProvider;
     78 import org.eclipse.jface.viewers.ITreeSelection;
     79 import org.eclipse.jface.viewers.StyledCellLabelProvider;
     80 import org.eclipse.jface.viewers.StyledString;
     81 import org.eclipse.jface.viewers.StyledString.Styler;
     82 import org.eclipse.jface.viewers.TreePath;
     83 import org.eclipse.jface.viewers.TreeSelection;
     84 import org.eclipse.jface.viewers.TreeViewer;
     85 import org.eclipse.jface.viewers.Viewer;
     86 import org.eclipse.jface.viewers.ViewerCell;
     87 import org.eclipse.swt.SWT;
     88 import org.eclipse.swt.dnd.DND;
     89 import org.eclipse.swt.dnd.Transfer;
     90 import org.eclipse.swt.events.DisposeEvent;
     91 import org.eclipse.swt.events.DisposeListener;
     92 import org.eclipse.swt.events.KeyEvent;
     93 import org.eclipse.swt.events.KeyListener;
     94 import org.eclipse.swt.events.MenuDetectEvent;
     95 import org.eclipse.swt.events.MenuDetectListener;
     96 import org.eclipse.swt.graphics.Image;
     97 import org.eclipse.swt.widgets.Composite;
     98 import org.eclipse.swt.widgets.Control;
     99 import org.eclipse.swt.widgets.Tree;
    100 import org.eclipse.swt.widgets.TreeItem;
    101 import org.eclipse.ui.IActionBars;
    102 import org.eclipse.ui.IEditorPart;
    103 import org.eclipse.ui.INullSelectionListener;
    104 import org.eclipse.ui.IWorkbenchPart;
    105 import org.eclipse.ui.actions.ActionFactory;
    106 import org.eclipse.ui.views.contentoutline.ContentOutlinePage;
    107 import org.eclipse.wb.core.controls.SelfOrientingSashForm;
    108 import org.eclipse.wb.internal.core.editor.structure.IPage;
    109 import org.eclipse.wb.internal.core.editor.structure.PageSiteComposite;
    110 import org.w3c.dom.Element;
    111 import org.w3c.dom.Node;
    112 
    113 import java.util.ArrayList;
    114 import java.util.HashSet;
    115 import java.util.List;
    116 import java.util.Set;
    117 
    118 /**
    119  * An outline page for the layout canvas view.
    120  * <p/>
    121  * The page is created by {@link LayoutEditorDelegate#delegateGetAdapter(Class)}. This means
    122  * we have *one* instance of the outline page per open canvas editor.
    123  * <p/>
    124  * It sets itself as a listener on the site's selection service in order to be
    125  * notified of the canvas' selection changes.
    126  * The underlying page is also a selection provider (via IContentOutlinePage)
    127  * and as such it will broadcast selection changes to the site's selection service
    128  * (on which both the layout editor part and the property sheet page listen.)
    129  */
    130 public class OutlinePage extends ContentOutlinePage
    131     implements INullSelectionListener, IPage {
    132 
    133     /** Label which separates outline text from additional attributes like text prefix or url */
    134     private static final String LABEL_SEPARATOR = " - ";
    135 
    136     /** Max character count in labels, used for truncation */
    137     private static final int LABEL_MAX_WIDTH = 50;
    138 
    139     /**
    140      * The graphical editor that created this outline.
    141      */
    142     private final GraphicalEditorPart mGraphicalEditorPart;
    143 
    144     /**
    145      * RootWrapper is a workaround: we can't set the input of the TreeView to its root
    146      * element, so we introduce a fake parent.
    147      */
    148     private final RootWrapper mRootWrapper = new RootWrapper();
    149 
    150     /**
    151      * Menu manager for the context menu actions.
    152      * The actions delegate to the current GraphicalEditorPart.
    153      */
    154     private MenuManager mMenuManager;
    155 
    156     private Composite mControl;
    157     private PropertySheetPage mPropertySheet;
    158     private PageSiteComposite mPropertySheetComposite;
    159     private boolean mShowPropertySheet;
    160     private boolean mShowHeader;
    161     private boolean mIgnoreSelection;
    162     private boolean mActive = true;
    163 
    164     /** Action to Select All in the tree */
    165     private final Action mTreeSelectAllAction = new Action() {
    166         @Override
    167         public void run() {
    168             getTreeViewer().getTree().selectAll();
    169             OutlinePage.this.fireSelectionChanged(getSelection());
    170         }
    171 
    172         @Override
    173         public String getId() {
    174             return ActionFactory.SELECT_ALL.getId();
    175         }
    176     };
    177 
    178     /** Action for moving items up in the tree */
    179     private Action mMoveUpAction = new Action("Move Up\t-",
    180             IconFactory.getInstance().getImageDescriptor("up")) { //$NON-NLS-1$
    181 
    182         @Override
    183         public String getId() {
    184             return "adt.outline.moveup"; //$NON-NLS-1$
    185         }
    186 
    187         @Override
    188         public boolean isEnabled() {
    189             return canMove(false);
    190         }
    191 
    192         @Override
    193         public void run() {
    194             move(false);
    195         }
    196     };
    197 
    198     /** Action for moving items down in the tree */
    199     private Action mMoveDownAction = new Action("Move Down\t+",
    200             IconFactory.getInstance().getImageDescriptor("down")) { //$NON-NLS-1$
    201 
    202         @Override
    203         public String getId() {
    204             return "adt.outline.movedown"; //$NON-NLS-1$
    205         }
    206 
    207         @Override
    208         public boolean isEnabled() {
    209             return canMove(true);
    210         }
    211 
    212         @Override
    213         public void run() {
    214             move(true);
    215         }
    216     };
    217 
    218     /**
    219      * Creates a new {@link OutlinePage} associated with the given editor
    220      *
    221      * @param graphicalEditorPart the editor associated with this outline
    222      */
    223     public OutlinePage(GraphicalEditorPart graphicalEditorPart) {
    224         super();
    225         mGraphicalEditorPart = graphicalEditorPart;
    226     }
    227 
    228     @Override
    229     public Control getControl() {
    230         // We've injected some controls between the root of the outline page
    231         // and the tree control, so return the actual root (a sash form) rather
    232         // than the superclass' implementation which returns the tree. If we don't
    233         // do this, various checks in the outline page which checks that getControl().getParent()
    234         // is the outline window itself will ignore this page.
    235         return mControl;
    236     }
    237 
    238     void setActive(boolean active) {
    239         if (active != mActive) {
    240             mActive = active;
    241 
    242             // Outlines are by default active when they are created; this is intended
    243             // for deactivating a hidden outline and later reactivating it
    244             assert mControl != null;
    245             if (active) {
    246                 getSite().getPage().addSelectionListener(this);
    247                 setModel(mGraphicalEditorPart.getCanvasControl().getViewHierarchy().getRoot());
    248             } else {
    249                 getSite().getPage().removeSelectionListener(this);
    250                 mRootWrapper.setRoot(null);
    251                 if (mPropertySheet != null) {
    252                     mPropertySheet.selectionChanged(null, TreeSelection.EMPTY);
    253                 }
    254             }
    255         }
    256     }
    257 
    258     /**
    259      * Set whether the outline should be shown in the header
    260      *
    261      * @param show whether a header should be shown
    262      */
    263     public void setShowHeader(boolean show) {
    264         mShowHeader = show;
    265     }
    266 
    267     /**
    268      * Set whether the property sheet should be shown within this outline
    269      *
    270      * @param show whether the property sheet should show
    271      */
    272     public void setShowPropertySheet(boolean show) {
    273         if (show != mShowPropertySheet) {
    274             mShowPropertySheet = show;
    275             if (mControl == null) {
    276                 return;
    277             }
    278 
    279             if (show && mPropertySheet == null) {
    280                 createPropertySheet();
    281             } else if (!show) {
    282                 mPropertySheetComposite.dispose();
    283                 mPropertySheetComposite = null;
    284                 mPropertySheet.dispose();
    285                 mPropertySheet = null;
    286             }
    287 
    288             mControl.layout();
    289         }
    290     }
    291 
    292     @Override
    293     public void createControl(Composite parent) {
    294         mControl = new SelfOrientingSashForm(parent, SWT.VERTICAL);
    295 
    296         if (mShowHeader) {
    297             PageSiteComposite mOutlineComposite = new PageSiteComposite(mControl, SWT.BORDER);
    298             mOutlineComposite.setTitleText("Outline");
    299             mOutlineComposite.setTitleImage(IconFactory.getInstance().getIcon("components_view"));
    300             mOutlineComposite.setPage(new IPage() {
    301                 @Override
    302                 public void createControl(Composite outlineParent) {
    303                     createOutline(outlineParent);
    304                 }
    305 
    306                 @Override
    307                 public void dispose() {
    308                 }
    309 
    310                 @Override
    311                 public Control getControl() {
    312                     return getTreeViewer().getTree();
    313                 }
    314 
    315                 @Override
    316                 public void setToolBar(IToolBarManager toolBarManager) {
    317                     makeContributions(null, toolBarManager, null);
    318                     toolBarManager.update(false);
    319                 }
    320 
    321                 @Override
    322                 public void setFocus() {
    323                     getControl().setFocus();
    324                 }
    325             });
    326         } else {
    327             createOutline(mControl);
    328         }
    329 
    330         if (mShowPropertySheet) {
    331             createPropertySheet();
    332         }
    333     }
    334 
    335     private void createOutline(Composite parent) {
    336         super.createControl(parent);
    337 
    338         TreeViewer tv = getTreeViewer();
    339         tv.setAutoExpandLevel(2);
    340         tv.setContentProvider(new ContentProvider());
    341         tv.setLabelProvider(new LabelProvider());
    342         tv.setInput(mRootWrapper);
    343         tv.expandToLevel(mRootWrapper.getRoot(), 2);
    344 
    345         int supportedOperations = DND.DROP_COPY | DND.DROP_MOVE;
    346         Transfer[] transfers = new Transfer[] {
    347             SimpleXmlTransfer.getInstance()
    348         };
    349 
    350         tv.addDropSupport(supportedOperations, transfers, new OutlineDropListener(this, tv));
    351         tv.addDragSupport(supportedOperations, transfers, new OutlineDragListener(this, tv));
    352 
    353         // The tree viewer will hold CanvasViewInfo instances, however these
    354         // change each time the canvas is reloaded. OTOH layoutlib gives us
    355         // constant UiView keys which we can use to perform tree item comparisons.
    356         tv.setComparer(new IElementComparer() {
    357             @Override
    358             public int hashCode(Object element) {
    359                 if (element instanceof CanvasViewInfo) {
    360                     UiViewElementNode key = ((CanvasViewInfo) element).getUiViewNode();
    361                     if (key != null) {
    362                         return key.hashCode();
    363                     }
    364                 }
    365                 if (element != null) {
    366                     return element.hashCode();
    367                 }
    368                 return 0;
    369             }
    370 
    371             @Override
    372             public boolean equals(Object a, Object b) {
    373                 if (a instanceof CanvasViewInfo && b instanceof CanvasViewInfo) {
    374                     UiViewElementNode keyA = ((CanvasViewInfo) a).getUiViewNode();
    375                     UiViewElementNode keyB = ((CanvasViewInfo) b).getUiViewNode();
    376                     if (keyA != null) {
    377                         return keyA.equals(keyB);
    378                     }
    379                 }
    380                 if (a != null) {
    381                     return a.equals(b);
    382                 }
    383                 return false;
    384             }
    385         });
    386         tv.addDoubleClickListener(new IDoubleClickListener() {
    387             @Override
    388             public void doubleClick(DoubleClickEvent event) {
    389                 // This used to open the property view, but now that properties are docked
    390                 // let's use it for something else -- such as showing the editor source
    391                 /*
    392                 // Front properties panel; its selection is already linked
    393                 IWorkbenchPage page = getSite().getPage();
    394                 try {
    395                     page.showView(IPageLayout.ID_PROP_SHEET, null, IWorkbenchPage.VIEW_ACTIVATE);
    396                 } catch (PartInitException e) {
    397                     AdtPlugin.log(e, "Could not activate property sheet");
    398                 }
    399                 */
    400 
    401                 TreeItem[] selection = getTreeViewer().getTree().getSelection();
    402                 if (selection.length > 0) {
    403                     CanvasViewInfo vi = getViewInfo(selection[0].getData());
    404                     if (vi != null) {
    405                         LayoutCanvas canvas = mGraphicalEditorPart.getCanvasControl();
    406                         canvas.show(vi);
    407                     }
    408                 }
    409             }
    410         });
    411 
    412         setupContextMenu();
    413 
    414         // Listen to selection changes from the layout editor
    415         getSite().getPage().addSelectionListener(this);
    416         getControl().addDisposeListener(new DisposeListener() {
    417 
    418             @Override
    419             public void widgetDisposed(DisposeEvent e) {
    420                 dispose();
    421             }
    422         });
    423 
    424         Tree tree = tv.getTree();
    425         tree.addKeyListener(new KeyListener() {
    426 
    427             @Override
    428             public void keyPressed(KeyEvent e) {
    429                 if (e.character == '-') {
    430                     if (mMoveUpAction.isEnabled()) {
    431                         mMoveUpAction.run();
    432                     }
    433                 } else if (e.character == '+') {
    434                     if (mMoveDownAction.isEnabled()) {
    435                         mMoveDownAction.run();
    436                     }
    437                 }
    438             }
    439 
    440             @Override
    441             public void keyReleased(KeyEvent e) {
    442             }
    443         });
    444     }
    445 
    446     private void createPropertySheet() {
    447         mPropertySheetComposite = new PageSiteComposite(mControl, SWT.BORDER);
    448         mPropertySheetComposite.setTitleText("Properties");
    449         mPropertySheetComposite.setTitleImage(IconFactory.getInstance().getIcon("properties_view"));
    450         mPropertySheet = new PropertySheetPage(mGraphicalEditorPart);
    451         mPropertySheetComposite.setPage(mPropertySheet);
    452     }
    453 
    454     @Override
    455     public void dispose() {
    456         mRootWrapper.setRoot(null);
    457 
    458         getSite().getPage().removeSelectionListener(this);
    459         super.dispose();
    460         if (mPropertySheet != null) {
    461             mPropertySheet.dispose();
    462             mPropertySheet = null;
    463         }
    464     }
    465 
    466     /**
    467      * Invoked by {@link LayoutCanvas} to set the model (a.k.a. the root view info).
    468      *
    469      * @param rootViewInfo The root of the view info hierarchy. Can be null.
    470      */
    471     public void setModel(CanvasViewInfo rootViewInfo) {
    472         if (!mActive) {
    473             return;
    474         }
    475 
    476         mRootWrapper.setRoot(rootViewInfo);
    477 
    478         TreeViewer tv = getTreeViewer();
    479         if (tv != null) {
    480             Object[] expanded = tv.getExpandedElements();
    481             tv.refresh();
    482             tv.setExpandedElements(expanded);
    483             // Ensure that the root is expanded
    484             tv.expandToLevel(rootViewInfo, 2);
    485         }
    486     }
    487 
    488     /**
    489      * Returns the current tree viewer selection. Shouldn't be null,
    490      * although it can be {@link TreeSelection#EMPTY}.
    491      */
    492     @Override
    493     public ISelection getSelection() {
    494         return super.getSelection();
    495     }
    496 
    497     /**
    498      * Sets the outline selection.
    499      *
    500      * @param selection Only {@link ITreeSelection} will be used, otherwise the
    501      *   selection will be cleared (including a null selection).
    502      */
    503     @Override
    504     public void setSelection(ISelection selection) {
    505         // TreeViewer should be able to deal with a null selection, but let's make it safe
    506         if (selection == null) {
    507             selection = TreeSelection.EMPTY;
    508         }
    509         if (selection.equals(TreeSelection.EMPTY)) {
    510             return;
    511         }
    512 
    513         super.setSelection(selection);
    514 
    515         TreeViewer tv = getTreeViewer();
    516         if (tv == null || !(selection instanceof ITreeSelection) || selection.isEmpty()) {
    517             return;
    518         }
    519 
    520         // auto-reveal the selection
    521         ITreeSelection treeSel = (ITreeSelection) selection;
    522         for (TreePath p : treeSel.getPaths()) {
    523             tv.expandToLevel(p, 1);
    524         }
    525     }
    526 
    527     @Override
    528     protected void fireSelectionChanged(ISelection selection) {
    529         super.fireSelectionChanged(selection);
    530         if (mPropertySheet != null && !mIgnoreSelection) {
    531             mPropertySheet.selectionChanged(null, selection);
    532         }
    533     }
    534 
    535     /**
    536      * Listens to a workbench selection.
    537      * Only listen on selection coming from {@link LayoutEditorDelegate}, which avoid
    538      * picking up our own selections.
    539      */
    540     @Override
    541     public void selectionChanged(IWorkbenchPart part, ISelection selection) {
    542         if (mIgnoreSelection) {
    543             return;
    544         }
    545 
    546         if (part instanceof IEditorPart) {
    547             LayoutEditorDelegate delegate = LayoutEditorDelegate.fromEditor((IEditorPart) part);
    548             if (delegate != null) {
    549                 try {
    550                     mIgnoreSelection = true;
    551                     setSelection(selection);
    552 
    553                     if (mPropertySheet != null) {
    554                         mPropertySheet.selectionChanged(part, selection);
    555                     }
    556                 } finally {
    557                     mIgnoreSelection = false;
    558                 }
    559             }
    560         }
    561     }
    562 
    563     // ----
    564 
    565     /**
    566      * In theory, the root of the model should be the input of the {@link TreeViewer},
    567      * which would be the root {@link CanvasViewInfo}.
    568      * That means in theory {@link ContentProvider#getElements(Object)} should return
    569      * its own input as the single root node.
    570      * <p/>
    571      * However as described in JFace Bug 9262, this case is not properly handled by
    572      * a {@link TreeViewer} and leads to an infinite recursion in the tree viewer.
    573      * See https://bugs.eclipse.org/bugs/show_bug.cgi?id=9262
    574      * <p/>
    575      * The solution is to wrap the tree viewer input in a dummy root node that acts
    576      * as a parent. This class does just that.
    577      */
    578     private static class RootWrapper {
    579         private CanvasViewInfo mRoot;
    580 
    581         public void setRoot(CanvasViewInfo root) {
    582             mRoot = root;
    583         }
    584 
    585         public CanvasViewInfo getRoot() {
    586             return mRoot;
    587         }
    588     }
    589 
    590     /** Return the {@link CanvasViewInfo} associated with the given TreeItem's data field */
    591     /* package */ static CanvasViewInfo getViewInfo(Object viewData) {
    592         if (viewData instanceof RootWrapper) {
    593             return ((RootWrapper) viewData).getRoot();
    594         }
    595         if (viewData instanceof CanvasViewInfo) {
    596             return (CanvasViewInfo) viewData;
    597         }
    598         return null;
    599     }
    600 
    601     // --- Content and Label Providers ---
    602 
    603     /**
    604      * Content provider for the Outline model.
    605      * Objects are going to be {@link CanvasViewInfo}.
    606      */
    607     private static class ContentProvider implements ITreeContentProvider {
    608 
    609         @Override
    610         public Object[] getChildren(Object element) {
    611             if (element instanceof RootWrapper) {
    612                 CanvasViewInfo root = ((RootWrapper)element).getRoot();
    613                 if (root != null) {
    614                     return new Object[] { root };
    615                 }
    616             }
    617             if (element instanceof CanvasViewInfo) {
    618                 List<CanvasViewInfo> children = ((CanvasViewInfo) element).getUniqueChildren();
    619                 if (children != null) {
    620                     return children.toArray();
    621                 }
    622             }
    623             return new Object[0];
    624         }
    625 
    626         @Override
    627         public Object getParent(Object element) {
    628             if (element instanceof CanvasViewInfo) {
    629                 return ((CanvasViewInfo) element).getParent();
    630             }
    631             return null;
    632         }
    633 
    634         @Override
    635         public boolean hasChildren(Object element) {
    636             if (element instanceof CanvasViewInfo) {
    637                 List<CanvasViewInfo> children = ((CanvasViewInfo) element).getChildren();
    638                 if (children != null) {
    639                     return children.size() > 0;
    640                 }
    641             }
    642             return false;
    643         }
    644 
    645         /**
    646          * Returns the root element.
    647          * Semantically, the root element is the single top-level XML element of the XML layout.
    648          */
    649         @Override
    650         public Object[] getElements(Object inputElement) {
    651             return getChildren(inputElement);
    652         }
    653 
    654         @Override
    655         public void dispose() {
    656             // pass
    657         }
    658 
    659         @Override
    660         public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
    661             // pass
    662         }
    663     }
    664 
    665     /**
    666      * Label provider for the Outline model.
    667      * Objects are going to be {@link CanvasViewInfo}.
    668      */
    669     private class LabelProvider extends StyledCellLabelProvider {
    670         /**
    671          * Returns the element's logo with a fallback on the android logo.
    672          *
    673          * @param element the tree element
    674          * @return the image to be used as a logo
    675          */
    676         public Image getImage(Object element) {
    677             if (element instanceof CanvasViewInfo) {
    678                 element = ((CanvasViewInfo) element).getUiViewNode();
    679             }
    680 
    681             if (element instanceof UiElementNode) {
    682                 UiElementNode node = (UiElementNode) element;
    683                 ElementDescriptor desc = node.getDescriptor();
    684                 if (desc != null) {
    685                     Image img = null;
    686                     // Special case for the common case of vertical linear layouts:
    687                     // show vertical linear icon (the default icon shows horizontal orientation)
    688                     String uiName = desc.getUiName();
    689                     if (uiName.equals(LINEAR_LAYOUT)) {
    690                         Element e = (Element) node.getXmlNode();
    691                         if (VALUE_VERTICAL.equals(e.getAttributeNS(ANDROID_URI,
    692                                 ATTR_ORIENTATION))) {
    693                             IconFactory factory = IconFactory.getInstance();
    694                             img = factory.getIcon("VerticalLinearLayout"); //$NON-NLS-1$
    695                         }
    696                     } else if (uiName.equals(VIEW_VIEWTAG)) {
    697                         Node xmlNode = node.getXmlNode();
    698                         if (xmlNode instanceof Element) {
    699                             String className = ((Element) xmlNode).getAttribute(ATTR_CLASS);
    700                             if (className != null && className.length() > 0) {
    701                                 int index = className.lastIndexOf('.');
    702                                 if (index != -1) {
    703                                     className = className.substring(index + 1);
    704                                 }
    705                                 img = IconFactory.getInstance().getIcon(className);
    706                             }
    707                         }
    708                     }
    709                     if (img == null) {
    710                         img = desc.getGenericIcon();
    711                     }
    712                     if (img != null) {
    713                         if (node.hasError()) {
    714                             return new ErrorImageComposite(img).createImage();
    715                         } else {
    716                             return img;
    717                         }
    718                     }
    719                 }
    720             }
    721 
    722             return AdtPlugin.getAndroidLogo();
    723         }
    724 
    725         /**
    726          * Uses {@link UiElementNode#getStyledDescription} for the label for this tree item.
    727          */
    728         @Override
    729         public void update(ViewerCell cell) {
    730             Object element = cell.getElement();
    731             StyledString styledString = null;
    732 
    733             CanvasViewInfo vi = null;
    734             if (element instanceof CanvasViewInfo) {
    735                 vi = (CanvasViewInfo) element;
    736                 element = vi.getUiViewNode();
    737             }
    738 
    739             Image image = getImage(element);
    740 
    741             if (element instanceof UiElementNode) {
    742                 UiElementNode node = (UiElementNode) element;
    743                 styledString = node.getStyledDescription();
    744                 Node xmlNode = node.getXmlNode();
    745                 if (xmlNode instanceof Element) {
    746                     Element e = (Element) xmlNode;
    747 
    748                     // Temporary diagnostics code when developing GridLayout
    749                     if (GridLayoutRule.sDebugGridLayout) {
    750                         String namespace;
    751                         if (e.getParentNode().getNodeName().equals(GRID_LAYOUT)) {
    752                             namespace = ANDROID_URI;
    753                         } else {
    754                             IProject project = mGraphicalEditorPart.getProject();
    755                             ProjectState projectState = Sdk.getProjectState(project);
    756                             if (projectState != null && projectState.isLibrary()) {
    757                                 namespace = AUTO_URI;
    758                             } else {
    759                                 ManifestInfo info = ManifestInfo.get(project);
    760                                 namespace = URI_PREFIX + info.getPackage();
    761                             }
    762                         }
    763 
    764                         if (e.getNodeName() != null && e.getNodeName().endsWith(GRID_LAYOUT)) {
    765                             // Attach rowCount/columnCount info
    766                             String rowCount = e.getAttributeNS(namespace, ATTR_ROW_COUNT);
    767                             if (rowCount.length() == 0) {
    768                                 rowCount = "?";
    769                             }
    770                             String columnCount = e.getAttributeNS(namespace, ATTR_COLUMN_COUNT);
    771                             if (columnCount.length() == 0) {
    772                                 columnCount = "?";
    773                             }
    774 
    775                             styledString.append(" - columnCount=", QUALIFIER_STYLER);
    776                             styledString.append(columnCount, QUALIFIER_STYLER);
    777                             styledString.append(", rowCount=", QUALIFIER_STYLER);
    778                             styledString.append(rowCount, QUALIFIER_STYLER);
    779                         } else if (e.getParentNode() != null
    780                             && e.getParentNode().getNodeName() != null
    781                             && e.getParentNode().getNodeName().endsWith(GRID_LAYOUT)) {
    782                             // Attach row/column info
    783                             String row = e.getAttributeNS(namespace, ATTR_LAYOUT_ROW);
    784                             if (row.length() == 0) {
    785                                 row = "?";
    786                             }
    787                             Styler colStyle = QUALIFIER_STYLER;
    788                             String column = e.getAttributeNS(namespace, ATTR_LAYOUT_COLUMN);
    789                             if (column.length() == 0) {
    790                                 column = "?";
    791                             } else {
    792                                 String colCount = ((Element) e.getParentNode()).getAttributeNS(
    793                                         namespace, ATTR_COLUMN_COUNT);
    794                                 if (colCount.length() > 0 && Integer.parseInt(colCount) <=
    795                                         Integer.parseInt(column)) {
    796                                     colStyle = StyledString.createColorRegistryStyler(
    797                                         JFacePreferences.ERROR_COLOR, null);
    798                                 }
    799                             }
    800                             String rowSpan = e.getAttributeNS(namespace, ATTR_LAYOUT_ROW_SPAN);
    801                             String columnSpan = e.getAttributeNS(namespace,
    802                                     ATTR_LAYOUT_COLUMN_SPAN);
    803                             if (rowSpan.length() == 0) {
    804                                 rowSpan = "1";
    805                             }
    806                             if (columnSpan.length() == 0) {
    807                                 columnSpan = "1";
    808                             }
    809 
    810                             styledString.append(" - cell (row=", QUALIFIER_STYLER);
    811                             styledString.append(row, QUALIFIER_STYLER);
    812                             styledString.append(',', QUALIFIER_STYLER);
    813                             styledString.append("col=", colStyle);
    814                             styledString.append(column, colStyle);
    815                             styledString.append(')', colStyle);
    816                             styledString.append(", span=(", QUALIFIER_STYLER);
    817                             styledString.append(columnSpan, QUALIFIER_STYLER);
    818                             styledString.append(',', QUALIFIER_STYLER);
    819                             styledString.append(rowSpan, QUALIFIER_STYLER);
    820                             styledString.append(')', QUALIFIER_STYLER);
    821                         }
    822                     }
    823 
    824                     if (e.hasAttributeNS(ANDROID_URI, ATTR_TEXT)) {
    825                         // Show the text attribute
    826                         String text = e.getAttributeNS(ANDROID_URI, ATTR_TEXT);
    827                         if (text != null && text.length() > 0
    828                                 && !text.contains(node.getDescriptor().getUiName())) {
    829                             if (text.charAt(0) == '@') {
    830                                 String resolved = mGraphicalEditorPart.findString(text);
    831                                 if (resolved != null) {
    832                                     text = resolved;
    833                                 }
    834                             }
    835                             styledString.append(LABEL_SEPARATOR, QUALIFIER_STYLER);
    836                             styledString.append('"', QUALIFIER_STYLER);
    837                             styledString.append(truncate(text, styledString), QUALIFIER_STYLER);
    838                             styledString.append('"', QUALIFIER_STYLER);
    839                         }
    840                     } else if (e.hasAttributeNS(ANDROID_URI, ATTR_SRC)) {
    841                         // Show ImageView source attributes etc
    842                         String src = e.getAttributeNS(ANDROID_URI, ATTR_SRC);
    843                         if (src != null && src.length() > 0) {
    844                             if (src.startsWith(DRAWABLE_PREFIX)) {
    845                                 src = src.substring(DRAWABLE_PREFIX.length());
    846                             }
    847                             styledString.append(LABEL_SEPARATOR, QUALIFIER_STYLER);
    848                             styledString.append(truncate(src, styledString), QUALIFIER_STYLER);
    849                         }
    850                     } else if (e.getTagName().equals(LayoutDescriptors.VIEW_INCLUDE)) {
    851                         // Show the include reference.
    852 
    853                         // Note: the layout attribute is NOT in the Android namespace
    854                         String src = e.getAttribute(LayoutDescriptors.ATTR_LAYOUT);
    855                         if (src != null && src.length() > 0) {
    856                             if (src.startsWith(LAYOUT_PREFIX)) {
    857                                 src = src.substring(LAYOUT_PREFIX.length());
    858                             }
    859                             styledString.append(LABEL_SEPARATOR, QUALIFIER_STYLER);
    860                             styledString.append(truncate(src, styledString), QUALIFIER_STYLER);
    861                         }
    862                     }
    863                 }
    864             } else if (element == null && vi != null) {
    865                 // It's an inclusion-context: display it
    866                 Reference includedWithin = mGraphicalEditorPart.getIncludedWithin();
    867                 if (includedWithin != null) {
    868                     styledString = new StyledString();
    869                     styledString.append(includedWithin.getDisplayName(), QUALIFIER_STYLER);
    870                     image = IconFactory.getInstance().getIcon(LayoutDescriptors.VIEW_INCLUDE);
    871                 }
    872             }
    873 
    874             if (styledString == null) {
    875                 styledString = new StyledString();
    876                 styledString.append(element == null ? "(null)" : element.toString());
    877             }
    878 
    879            cell.setText(styledString.toString());
    880            cell.setStyleRanges(styledString.getStyleRanges());
    881            cell.setImage(image);
    882            super.update(cell);
    883        }
    884 
    885         @Override
    886         public boolean isLabelProperty(Object element, String property) {
    887             return super.isLabelProperty(element, property);
    888         }
    889     }
    890 
    891     // --- Context Menu ---
    892 
    893     /**
    894      * This viewer uses its own actions that delegate to the ones given
    895      * by the {@link LayoutCanvas}. All the processing is actually handled
    896      * directly by the canvas and this viewer only gets refreshed as a
    897      * consequence of the canvas changing the XML model.
    898      */
    899     private void setupContextMenu() {
    900 
    901         mMenuManager = new MenuManager();
    902         mMenuManager.removeAll();
    903 
    904         mMenuManager.add(mMoveUpAction);
    905         mMenuManager.add(mMoveDownAction);
    906         mMenuManager.add(new Separator());
    907 
    908         mMenuManager.add(new SelectionManager.SelectionMenu(mGraphicalEditorPart));
    909         mMenuManager.add(new Separator());
    910         final String prefix = LayoutCanvas.PREFIX_CANVAS_ACTION;
    911         mMenuManager.add(new DelegateAction(prefix + ActionFactory.CUT.getId()));
    912         mMenuManager.add(new DelegateAction(prefix + ActionFactory.COPY.getId()));
    913         mMenuManager.add(new DelegateAction(prefix + ActionFactory.PASTE.getId()));
    914 
    915         mMenuManager.add(new Separator());
    916 
    917         mMenuManager.add(new DelegateAction(prefix + ActionFactory.DELETE.getId()));
    918 
    919         mMenuManager.addMenuListener(new IMenuListener() {
    920             @Override
    921             public void menuAboutToShow(IMenuManager manager) {
    922                 // Update all actions to match their LayoutCanvas counterparts
    923                 for (IContributionItem contrib : manager.getItems()) {
    924                     if (contrib instanceof ActionContributionItem) {
    925                         IAction action = ((ActionContributionItem) contrib).getAction();
    926                         if (action instanceof DelegateAction) {
    927                             ((DelegateAction) action).updateFromEditorPart(mGraphicalEditorPart);
    928                         }
    929                     }
    930                 }
    931             }
    932         });
    933 
    934         new DynamicContextMenu(
    935                 mGraphicalEditorPart.getEditorDelegate(),
    936                 mGraphicalEditorPart.getCanvasControl(),
    937                 mMenuManager);
    938 
    939         getTreeViewer().getTree().setMenu(mMenuManager.createContextMenu(getControl()));
    940 
    941         // Update Move Up/Move Down state only when the menu is opened
    942         getTreeViewer().getTree().addMenuDetectListener(new MenuDetectListener() {
    943             @Override
    944             public void menuDetected(MenuDetectEvent e) {
    945                 mMenuManager.update(IAction.ENABLED);
    946             }
    947         });
    948     }
    949 
    950     /**
    951      * An action that delegates its properties and behavior to a target action.
    952      * The target action can be null or it can change overtime, typically as the
    953      * layout canvas' editor part is activated or closed.
    954      */
    955     private static class DelegateAction extends Action {
    956         private IAction mTargetAction;
    957         private final String mCanvasActionId;
    958 
    959         public DelegateAction(String canvasActionId) {
    960             super(canvasActionId);
    961             setId(canvasActionId);
    962             mCanvasActionId = canvasActionId;
    963         }
    964 
    965         // --- Methods form IAction ---
    966 
    967         /** Returns the target action's {@link #isEnabled()} if defined, or false. */
    968         @Override
    969         public boolean isEnabled() {
    970             return mTargetAction == null ? false : mTargetAction.isEnabled();
    971         }
    972 
    973         /** Returns the target action's {@link #isChecked()} if defined, or false. */
    974         @Override
    975         public boolean isChecked() {
    976             return mTargetAction == null ? false : mTargetAction.isChecked();
    977         }
    978 
    979         /** Returns the target action's {@link #isHandled()} if defined, or false. */
    980         @Override
    981         public boolean isHandled() {
    982             return mTargetAction == null ? false : mTargetAction.isHandled();
    983         }
    984 
    985         /** Runs the target action if defined. */
    986         @Override
    987         public void run() {
    988             if (mTargetAction != null) {
    989                 mTargetAction.run();
    990             }
    991             super.run();
    992         }
    993 
    994         /**
    995          * Updates this action to delegate to its counterpart in the given editor part
    996          *
    997          * @param editorPart The editor being updated
    998          */
    999         public void updateFromEditorPart(GraphicalEditorPart editorPart) {
   1000             LayoutCanvas canvas = editorPart == null ? null : editorPart.getCanvasControl();
   1001             if (canvas == null) {
   1002                 mTargetAction = null;
   1003             } else {
   1004                 mTargetAction = canvas.getAction(mCanvasActionId);
   1005             }
   1006 
   1007             if (mTargetAction != null) {
   1008                 setText(mTargetAction.getText());
   1009                 setId(mTargetAction.getId());
   1010                 setDescription(mTargetAction.getDescription());
   1011                 setImageDescriptor(mTargetAction.getImageDescriptor());
   1012                 setHoverImageDescriptor(mTargetAction.getHoverImageDescriptor());
   1013                 setDisabledImageDescriptor(mTargetAction.getDisabledImageDescriptor());
   1014                 setToolTipText(mTargetAction.getToolTipText());
   1015                 setActionDefinitionId(mTargetAction.getActionDefinitionId());
   1016                 setHelpListener(mTargetAction.getHelpListener());
   1017                 setAccelerator(mTargetAction.getAccelerator());
   1018                 setChecked(mTargetAction.isChecked());
   1019                 setEnabled(mTargetAction.isEnabled());
   1020             } else {
   1021                 setEnabled(false);
   1022             }
   1023         }
   1024     }
   1025 
   1026     /** Returns the associated editor with this outline */
   1027     /* package */GraphicalEditorPart getEditor() {
   1028         return mGraphicalEditorPart;
   1029     }
   1030 
   1031     @Override
   1032     public void setActionBars(IActionBars actionBars) {
   1033         super.setActionBars(actionBars);
   1034 
   1035         // Map Outline actions to canvas actions such that they share Undo context etc
   1036         LayoutCanvas canvas = mGraphicalEditorPart.getCanvasControl();
   1037         canvas.updateGlobalActions(actionBars);
   1038 
   1039         // Special handling for Select All since it's different than the canvas (will
   1040         // include selecting the root etc)
   1041         actionBars.setGlobalActionHandler(mTreeSelectAllAction.getId(), mTreeSelectAllAction);
   1042         actionBars.updateActionBars();
   1043     }
   1044 
   1045     // ---- Move Up/Down Support ----
   1046 
   1047     /** Returns true if the current selected item can be moved */
   1048     private boolean canMove(boolean forward) {
   1049         CanvasViewInfo viewInfo = getSingleSelectedItem();
   1050         if (viewInfo != null) {
   1051             UiViewElementNode node = viewInfo.getUiViewNode();
   1052             if (forward) {
   1053                 return findNext(node) != null;
   1054             } else {
   1055                 return findPrevious(node) != null;
   1056             }
   1057         }
   1058 
   1059         return false;
   1060     }
   1061 
   1062     /** Moves the current selected item down (forward) or up (not forward) */
   1063     private void move(boolean forward) {
   1064         CanvasViewInfo viewInfo = getSingleSelectedItem();
   1065         if (viewInfo != null) {
   1066             final Pair<UiViewElementNode, Integer> target;
   1067             UiViewElementNode selected = viewInfo.getUiViewNode();
   1068             if (forward) {
   1069                 target = findNext(selected);
   1070             } else {
   1071                 target = findPrevious(selected);
   1072             }
   1073             if (target != null) {
   1074                 final LayoutCanvas canvas = mGraphicalEditorPart.getCanvasControl();
   1075                 final SelectionManager selectionManager = canvas.getSelectionManager();
   1076                 final ArrayList<SelectionItem> dragSelection = new ArrayList<SelectionItem>();
   1077                 dragSelection.add(selectionManager.createSelection(viewInfo));
   1078                 SelectionManager.sanitize(dragSelection);
   1079 
   1080                 if (!dragSelection.isEmpty()) {
   1081                     final SimpleElement[] elements = SelectionItem.getAsElements(dragSelection);
   1082                     UiViewElementNode parentNode = target.getFirst();
   1083                     final NodeProxy targetNode = canvas.getNodeFactory().create(parentNode);
   1084 
   1085                     // Record children of the target right before the drop (such that we
   1086                     // can find out after the drop which exact children were inserted)
   1087                     Set<INode> children = new HashSet<INode>();
   1088                     for (INode node : targetNode.getChildren()) {
   1089                         children.add(node);
   1090                     }
   1091 
   1092                     String label = MoveGesture.computeUndoLabel(targetNode,
   1093                             elements, DND.DROP_MOVE);
   1094                     canvas.getEditorDelegate().getEditor().wrapUndoEditXmlModel(label, new Runnable() {
   1095                         @Override
   1096                         public void run() {
   1097                             InsertType insertType = InsertType.MOVE_INTO;
   1098                             if (dragSelection.get(0).getNode().getParent() == targetNode) {
   1099                                 insertType = InsertType.MOVE_WITHIN;
   1100                             }
   1101                             canvas.getRulesEngine().setInsertType(insertType);
   1102                             int index = target.getSecond();
   1103                             BaseLayoutRule.insertAt(targetNode, elements, false, index);
   1104                             targetNode.applyPendingChanges();
   1105                             canvas.getClipboardSupport().deleteSelection("Remove", dragSelection);
   1106                         }
   1107                     });
   1108 
   1109                     // Now find out which nodes were added, and look up their
   1110                     // corresponding CanvasViewInfos
   1111                     final List<INode> added = new ArrayList<INode>();
   1112                     for (INode node : targetNode.getChildren()) {
   1113                         if (!children.contains(node)) {
   1114                             added.add(node);
   1115                         }
   1116                     }
   1117 
   1118                     selectionManager.setOutlineSelection(added);
   1119                 }
   1120             }
   1121         }
   1122     }
   1123 
   1124     /**
   1125      * Returns the {@link CanvasViewInfo} for the currently selected item, or null if
   1126      * there are no or multiple selected items
   1127      *
   1128      * @return the current selected item if there is exactly one item selected
   1129      */
   1130     private CanvasViewInfo getSingleSelectedItem() {
   1131         TreeItem[] selection = getTreeViewer().getTree().getSelection();
   1132         if (selection.length == 1) {
   1133             return getViewInfo(selection[0].getData());
   1134         }
   1135 
   1136         return null;
   1137     }
   1138 
   1139 
   1140     /** Returns the pair [parent,index] of the next node (when iterating forward) */
   1141     @VisibleForTesting
   1142     /* package */ static Pair<UiViewElementNode, Integer> findNext(UiViewElementNode node) {
   1143         UiElementNode parent = node.getUiParent();
   1144         if (parent == null) {
   1145             return null;
   1146         }
   1147 
   1148         UiElementNode next = node.getUiNextSibling();
   1149         if (next != null) {
   1150             if (DescriptorsUtils.canInsertChildren(next.getDescriptor(), null)) {
   1151                 return getFirstPosition(next);
   1152             } else {
   1153                 return getPositionAfter(next);
   1154             }
   1155         }
   1156 
   1157         next = parent.getUiNextSibling();
   1158         if (next != null) {
   1159             return getPositionBefore(next);
   1160         } else {
   1161             UiElementNode grandParent = parent.getUiParent();
   1162             if (grandParent != null) {
   1163                 return getLastPosition(grandParent);
   1164             }
   1165         }
   1166 
   1167         return null;
   1168     }
   1169 
   1170     /** Returns the pair [parent,index] of the previous node (when iterating backward) */
   1171     @VisibleForTesting
   1172     /* package */ static Pair<UiViewElementNode, Integer> findPrevious(UiViewElementNode node) {
   1173         UiElementNode prev = node.getUiPreviousSibling();
   1174         if (prev != null) {
   1175             UiElementNode curr = prev;
   1176             while (true) {
   1177                 List<UiElementNode> children = curr.getUiChildren();
   1178                 if (children.size() > 0) {
   1179                     curr = children.get(children.size() - 1);
   1180                     continue;
   1181                 }
   1182                 if (DescriptorsUtils.canInsertChildren(curr.getDescriptor(), null)) {
   1183                     return getFirstPosition(curr);
   1184                 } else {
   1185                     if (curr == prev) {
   1186                         return getPositionBefore(curr);
   1187                     } else {
   1188                         return getPositionAfter(curr);
   1189                     }
   1190                 }
   1191             }
   1192         }
   1193 
   1194         return getPositionBefore(node.getUiParent());
   1195     }
   1196 
   1197     /** Returns the pair [parent,index] of the position immediately before the given node  */
   1198     private static Pair<UiViewElementNode, Integer> getPositionBefore(UiElementNode node) {
   1199         if (node != null) {
   1200             UiElementNode parent = node.getUiParent();
   1201             if (parent != null && parent instanceof UiViewElementNode) {
   1202                 return Pair.of((UiViewElementNode) parent, node.getUiSiblingIndex());
   1203             }
   1204         }
   1205 
   1206         return null;
   1207     }
   1208 
   1209     /** Returns the pair [parent,index] of the position immediately following the given node  */
   1210     private static Pair<UiViewElementNode, Integer> getPositionAfter(UiElementNode node) {
   1211         if (node != null) {
   1212             UiElementNode parent = node.getUiParent();
   1213             if (parent != null && parent instanceof UiViewElementNode) {
   1214                 return Pair.of((UiViewElementNode) parent, node.getUiSiblingIndex() + 1);
   1215             }
   1216         }
   1217 
   1218         return null;
   1219     }
   1220 
   1221     /** Returns the pair [parent,index] of the first position inside the given parent */
   1222     private static Pair<UiViewElementNode, Integer> getFirstPosition(UiElementNode parent) {
   1223         if (parent != null && parent instanceof UiViewElementNode) {
   1224             return Pair.of((UiViewElementNode) parent, 0);
   1225         }
   1226 
   1227         return null;
   1228     }
   1229 
   1230     /**
   1231      * Returns the pair [parent,index] of the last position after the given node's
   1232      * children
   1233      */
   1234     private static Pair<UiViewElementNode, Integer> getLastPosition(UiElementNode parent) {
   1235         if (parent != null && parent instanceof UiViewElementNode) {
   1236             return Pair.of((UiViewElementNode) parent, parent.getUiChildren().size());
   1237         }
   1238 
   1239         return null;
   1240     }
   1241 
   1242     /**
   1243      * Truncates the given text such that it will fit into the given {@link StyledString}
   1244      * up to a maximum length of {@link #LABEL_MAX_WIDTH}.
   1245      *
   1246      * @param text the text to truncate
   1247      * @param string the existing string to be appended to
   1248      * @return the truncated string
   1249      */
   1250     private static String truncate(String text, StyledString string) {
   1251         int existingLength = string.length();
   1252 
   1253         if (text.length() + existingLength > LABEL_MAX_WIDTH) {
   1254             int truncatedLength = LABEL_MAX_WIDTH - existingLength - 3;
   1255             if (truncatedLength > 0) {
   1256                 return String.format("%1$s...", text.substring(0, truncatedLength));
   1257             } else {
   1258                 return ""; //$NON-NLS-1$
   1259             }
   1260         }
   1261 
   1262         return text;
   1263     }
   1264 
   1265     @Override
   1266     public void setToolBar(IToolBarManager toolBarManager) {
   1267         makeContributions(null, toolBarManager, null);
   1268         toolBarManager.update(false);
   1269     }
   1270 }
   1271