FileDocCategorySizeDatePackage
UiTreeBlock.javaAPI DocAndroid 1.5 API36838Wed May 06 22:41:10 BST 2009com.android.ide.eclipse.editors.ui.tree

UiTreeBlock

public final class UiTreeBlock extends org.eclipse.ui.forms.MasterDetailsBlock implements ICommitXml
{@link UiTreeBlock} is a {@link MasterDetailsBlock} which displays a tree view for a specific set of {@link UiElementNode}.

For a given UI element node, the tree view displays all first-level children that match a given type (given by an {@link ElementDescriptor}. All children from these nodes are also displayed.

In the middle next to the tree are some controls to add or delete tree nodes. On the left is a details part that displays all the visible UI attributes for a given selected UI element node.

Fields Summary
private static final int
TREE_HEIGHT_HINT
Height hint for the tree view. Helps the grid layout resize properly on smaller screens.
com.android.ide.eclipse.editors.AndroidEditor
mEditor
Container editor
private com.android.ide.eclipse.editors.uimodel.UiElementNode
mUiRootNode
The root {@link UiElementNode} which contains all the elements that are to be manipulated by this tree view. In general this is the manifest UI node.
private com.android.ide.eclipse.editors.descriptors.ElementDescriptor[]
mDescriptorFilters
The descriptor of the elements to be displayed as root in this tree view. All elements of the same type in the root will be displayed.
private String
mTitle
The title for the master-detail part (displayed on the top "tab" on top of the tree)
private String
mDescription
The description for the master-detail part (displayed on top of the tree view)
private com.android.ide.eclipse.editors.ui.SectionHelper.ManifestSectionPart
mMasterPart
The master-detail part, composed of a main tree and an auxiliary detail part
private org.eclipse.jface.viewers.TreeViewer
mTreeViewer
The tree viewer in the master-detail part
private org.eclipse.swt.widgets.Button
mAddButton
The "add" button for the tree view
private org.eclipse.swt.widgets.Button
mRemoveButton
The "remove" button for the tree view
private org.eclipse.swt.widgets.Button
mUpButton
The "up" button for the tree view
private org.eclipse.swt.widgets.Button
mDownButton
The "down" button for the tree view
private org.eclipse.ui.forms.IManagedForm
mManagedForm
The Managed Form used to create the master part
private org.eclipse.ui.forms.DetailsPart
mDetailsPart
Reference to the details part of the tree master block.
private org.eclipse.swt.dnd.Clipboard
mClipboard
Reference to the clipboard for copy-paste
private com.android.ide.eclipse.editors.uimodel.IUiUpdateListener
mUiRefreshListener
Listener to refresh the tree viewer when the parent's node has been updated
private com.android.ide.eclipse.editors.uimodel.IUiUpdateListener
mUiEnableListener
Listener to enable/disable the UI based on the application node's presence
private UiTreeActions
mUiTreeActions
An adapter/wrapper to use the add/remove/up/down tree edit actions.
private final boolean
mAutoCreateRoot
True if the root node can be created on-demand (i.e. as needed as soon as children exist). False if an external entity controls the existence of the root node. In practise, this is false for the manifest application page (the actual "application" node is managed by the ApplicationToggle part) whereas it is true for all other tree pages.
Constructors Summary
public UiTreeBlock(com.android.ide.eclipse.editors.AndroidEditor editor, com.android.ide.eclipse.editors.uimodel.UiElementNode uiRootNode, boolean autoCreateRoot, com.android.ide.eclipse.editors.descriptors.ElementDescriptor[] descriptorFilters, String title, String description)
Creates a new {@link MasterDetailsBlock} that will display all UI nodes matching the given filter in the given root node.

param
editor The parent manifest editor.
param
uiRootNode The root {@link UiElementNode} which contains all the elements that are to be manipulated by this tree view. In general this is the manifest UI node or the application UI node. This cannot be null.
param
autoCreateRoot True if the root node can be created on-demand (i.e. as needed as soon as children exist). False if an external entity controls the existence of the root node. In practise, this is false for the manifest application page (the actual "application" node is managed by the ApplicationToggle part) whereas it is true for all other tree pages.
param
descriptorFilters A list of descriptors of the elements to be displayed as root in this tree view. Use null or an empty list to accept any kind of node.
param
title Title for the section
param
description Description for the section



                                                                                                                                                                                                                                
      
             
             
             
             
              
        mEditor = editor;
        mUiRootNode = uiRootNode;
        mAutoCreateRoot = autoCreateRoot;
        mDescriptorFilters = descriptorFilters;
        mTitle = title;
        mDescription = description;
    
Methods Summary
private voidadjustTreeButtons(org.eclipse.jface.viewers.ISelection selection)
This is called by the tree when a selection is made. It enables/disables the buttons associated with the tree depending on the current selection.

param
selection The current tree selection (same as mTreeViewer.getSelection())

        mRemoveButton.setEnabled(!selection.isEmpty() && selection instanceof ITreeSelection);
        mUpButton.setEnabled(!selection.isEmpty() && selection instanceof ITreeSelection);
        mDownButton.setEnabled(!selection.isEmpty() && selection instanceof ITreeSelection);
    
public voidchangeRootAndDescriptors(com.android.ide.eclipse.editors.uimodel.UiElementNode uiRootNode, com.android.ide.eclipse.editors.descriptors.ElementDescriptor[] descriptorFilters, boolean forceRefresh)
Changes the UI root node and the descriptor filters of the tree.

This removes the listeners attached to the old root node and reattaches them to the new one.

param
uiRootNode The root {@link UiElementNode} which contains all the elements that are to be manipulated by this tree view. In general this is the manifest UI node or the application UI node. This cannot be null.
param
descriptorFilters A list of descriptors of the elements to be displayed as root in this tree view. Use null or an empty list to accept any kind of node.
param
forceRefresh If tree, forces the tree to refresh

        UiElementNode node;

        // Remove previous listeners if any
        if (mUiRootNode != null) {
            node = mUiRootNode.getUiParent() != null ? mUiRootNode.getUiParent() : mUiRootNode;
            node.removeUpdateListener(mUiRefreshListener);
            mUiRootNode.removeUpdateListener(mUiEnableListener);
        }
        
        mUiRootNode = uiRootNode;
        mDescriptorFilters = descriptorFilters;

        mTreeViewer.setContentProvider(new UiModelTreeContentProvider(mUiRootNode, mDescriptorFilters));

        // Listen on structural changes on the root node of the tree
        // If the node has a parent, listen on the parent instead.
        node = mUiRootNode.getUiParent() != null ? mUiRootNode.getUiParent() : mUiRootNode;
        node.addUpdateListener(mUiRefreshListener);
        
        // Use the root node to listen to its presence.
        mUiRootNode.addUpdateListener(mUiEnableListener);

        // Initialize the enabled/disabled state
        mUiEnableListener.uiElementNodeUpdated(mUiRootNode, null /* state, not used */);
        
        if (forceRefresh) {
            mTreeViewer.refresh();
        }

        createSectionActions(mMasterPart.getSection(), mManagedForm.getToolkit());
    
voidcommitManagedForm()
Commits the current managed form (the one associated with our master part). As a side effect, this will commit the current UiElementDetails page.

        if (mManagedForm != null) {
            mManagedForm.commit(false /* onSave */);
        }
    
public voidcommitPendingXmlChanges()

        commitManagedForm();
    
private voidcreateButtons(org.eclipse.ui.forms.widgets.FormToolkit toolkit, org.eclipse.swt.widgets.Composite grid)
Creates the buttons next to the tree.

        
        mUiTreeActions = new UiTreeActions();
        
        Composite button_grid = SectionHelper.createGridLayout(grid, toolkit, 1);
        button_grid.setLayoutData(new GridData(GridData.VERTICAL_ALIGN_BEGINNING));
        mAddButton = toolkit.createButton(button_grid, "Add...", SWT.PUSH);
        SectionHelper.addControlTooltip(mAddButton, "Adds a new element.");
        mAddButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL |
                GridData.VERTICAL_ALIGN_BEGINNING));

        mAddButton.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                super.widgetSelected(e);
                doTreeAdd();
            }
        });
        
        mRemoveButton = toolkit.createButton(button_grid, "Remove...", SWT.PUSH);
        SectionHelper.addControlTooltip(mRemoveButton, "Removes an existing selected element.");
        mRemoveButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
        
        mRemoveButton.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                super.widgetSelected(e);
                doTreeRemove();
            }
        });
        
        mUpButton = toolkit.createButton(button_grid, "Up", SWT.PUSH);
        SectionHelper.addControlTooltip(mRemoveButton, "Moves the selected element up.");
        mUpButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
        
        mUpButton.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                super.widgetSelected(e);
                doTreeUp();
            }
        });

        mDownButton = toolkit.createButton(button_grid, "Down", SWT.PUSH);
        SectionHelper.addControlTooltip(mRemoveButton, "Moves the selected element down.");
        mDownButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
        
        mDownButton.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                super.widgetSelected(e);
                doTreeDown();
            }
        });

        adjustTreeButtons(TreeSelection.EMPTY);
    
protected voidcreateMasterPart(org.eclipse.ui.forms.IManagedForm managedForm, org.eclipse.swt.widgets.Composite parent)

        FormToolkit toolkit = managedForm.getToolkit();

        mManagedForm = managedForm;
        mMasterPart = new ManifestSectionPart(parent, toolkit);
        Section section = mMasterPart.getSection();
        section.setText(mTitle);
        section.setDescription(mDescription);
        section.setLayout(new GridLayout());
        section.setLayoutData(new GridData(GridData.FILL_BOTH));

        Composite grid = SectionHelper.createGridLayout(section, toolkit, 2);

        Tree tree = createTreeViewer(toolkit, grid, managedForm);
        createButtons(toolkit, grid);
        createTreeContextMenu(tree);
        createSectionActions(section, toolkit);
    
private voidcreateSectionActions(org.eclipse.ui.forms.widgets.Section section, org.eclipse.ui.forms.widgets.FormToolkit toolkit)

        ToolBarManager manager = new ToolBarManager(SWT.FLAT);
        manager.removeAll();
        
        ToolBar toolbar = manager.createControl(section);        
        section.setTextClient(toolbar);
        
        ElementDescriptor[] descs = mDescriptorFilters;
        if (descs == null && mUiRootNode != null) {
            descs = mUiRootNode.getDescriptor().getChildren();
        }
        
        if (descs != null && descs.length > 1) {
            for (ElementDescriptor desc : descs) {
                manager.add(new DescriptorFilterAction(desc));
            }
        }
        
        manager.add(new TreeSortAction());

        manager.update(true /*force*/);
    
protected voidcreateToolBarActions(org.eclipse.ui.forms.IManagedForm managedForm)

        // Pass. Not used, toolbar actions are defined by createSectionActions().
    
private voidcreateTreeContextMenu(org.eclipse.swt.widgets.Tree tree)

        MenuManager menuManager = new MenuManager();
        menuManager.setRemoveAllWhenShown(true);
        menuManager.addMenuListener(new IMenuListener() {
            /**
             * The menu is about to be shown. The menu manager has already been
             * requested to remove any existing menu item. This method gets the
             * tree selection and if it is of the appropriate type it re-creates
             * the necessary actions.
             */
           public void menuAboutToShow(IMenuManager manager) {
               ISelection selection = mTreeViewer.getSelection();
               if (!selection.isEmpty() && selection instanceof ITreeSelection) {
                   ArrayList<UiElementNode> selected = filterSelection((ITreeSelection) selection);
                   doCreateMenuAction(manager, selected);
                   return;
               }
               doCreateMenuAction(manager, null /* ui_node */);
            } 
        });
        Menu contextMenu = menuManager.createContextMenu(tree);
        tree.setMenu(contextMenu);
    
private org.eclipse.swt.widgets.TreecreateTreeViewer(org.eclipse.ui.forms.widgets.FormToolkit toolkit, org.eclipse.swt.widgets.Composite grid, org.eclipse.ui.forms.IManagedForm managedForm)
Creates the tree and its viewer

return
The tree control

        // Note: we *could* use a FilteredTree instead of the Tree+TreeViewer here.
        // However the class must be adapted to create an adapted toolkit tree.
        final Tree tree = toolkit.createTree(grid, SWT.MULTI);
        GridData gd = new GridData(GridData.FILL_BOTH);
        gd.widthHint = AndroidEditor.TEXT_WIDTH_HINT;
        gd.heightHint = TREE_HEIGHT_HINT;
        tree.setLayoutData(gd);

        mTreeViewer = new TreeViewer(tree);
        mTreeViewer.setContentProvider(new UiModelTreeContentProvider(mUiRootNode, mDescriptorFilters));
        mTreeViewer.setLabelProvider(new UiModelTreeLabelProvider());
        mTreeViewer.setInput("unused"); //$NON-NLS-1$

        // Create a listener that reacts to selections on the tree viewer.
        // When a selection is made, ask the managed form to propagate an event to
        // all parts in the managed form.
        // This is picked up by UiElementDetail.selectionChanged().
        mTreeViewer.addSelectionChangedListener(new ISelectionChangedListener() {
            public void selectionChanged(SelectionChangedEvent event) {
                managedForm.fireSelectionChanged(mMasterPart, event.getSelection());
                adjustTreeButtons(event.getSelection());
            }
        });
        
        // Create three listeners:
        // - One to refresh the tree viewer when the parent's node has been updated
        // - One to refresh the tree viewer when the framework resources have changed
        // - One to enable/disable the UI based on the application node's presence.
        mUiRefreshListener = new IUiUpdateListener() {
            public void uiElementNodeUpdated(UiElementNode ui_node, UiUpdateState state) {
                mTreeViewer.refresh();
            }
        };
        
        mUiEnableListener = new IUiUpdateListener() {
            public void uiElementNodeUpdated(UiElementNode ui_node, UiUpdateState state) {
                // The UiElementNode for the application XML node always exists, even
                // if there is no corresponding XML node in the XML file.
                //
                // Normally, we enable the UI here if the XML node is not null.
                //
                // However if mAutoCreateRoot is true, the root node will be created on-demand
                // so the tree/block is always enabled.
                boolean exists = mAutoCreateRoot || (ui_node.getXmlNode() != null);
                if (mMasterPart != null) {
                    Section section = mMasterPart.getSection();
                    if (section.getEnabled() != exists) {
                        section.setEnabled(exists);
                        for (Control c : section.getChildren()) {
                            c.setEnabled(exists);
                        }
                    }
                }
            }
        };

        /** Listener to update the root node if the target of the file is changed because of a
         * SDK location change or a project target change */
        final ITargetChangeListener targetListener = new ITargetChangeListener() {
            public void onProjectTargetChange(IProject changedProject) {
                if (changedProject == mEditor.getProject()) {
                    onTargetsLoaded();
                }
            }

            public void onTargetsLoaded() {
                // If a details part has been created, we need to "refresh" it too.
                if (mDetailsPart != null) {
                    // The details part does not directly expose access to its internal
                    // page book. Instead it is possible to resize the page book to 0 and then
                    // back to its original value, which has the side effect of removing all
                    // existing cached pages.
                    int limit = mDetailsPart.getPageLimit();
                    mDetailsPart.setPageLimit(0);
                    mDetailsPart.setPageLimit(limit);
                }
                // Refresh the tree, preserving the selection if possible.
                mTreeViewer.refresh();
            }
        };

        // Setup the listeners
        changeRootAndDescriptors(mUiRootNode, mDescriptorFilters, false /* refresh */);

        // Listen on resource framework changes to refresh the tree
        AdtPlugin.getDefault().addTargetListener(targetListener);

        // Remove listeners when the tree widget gets disposed.
        tree.addDisposeListener(new DisposeListener() {
            public void widgetDisposed(DisposeEvent e) {
                UiElementNode node = mUiRootNode.getUiParent() != null ?
                                        mUiRootNode.getUiParent() :
                                        mUiRootNode;

                node.removeUpdateListener(mUiRefreshListener);
                mUiRootNode.removeUpdateListener(mUiEnableListener);

                AdtPlugin.getDefault().removeTargetListener(targetListener);
                if (mClipboard != null) {
                    mClipboard.dispose();
                    mClipboard = null;
                }
            }
        });
        
        // Get a new clipboard reference. It is disposed when the tree is disposed.
        mClipboard = new Clipboard(tree.getDisplay());

        return tree;
    
private voiddoCreateMenuAction(org.eclipse.jface.action.IMenuManager manager, java.util.ArrayList selected)
Adds the menu actions to the context menu when the given UI node is selected in the tree view.

param
manager The context menu manager
param
selected The UI nodes selected in the tree. Can be null, in which case the root is to be modified.

        if (selected != null) {
            boolean hasXml = false;
            for (UiElementNode uiNode : selected) {
                if (uiNode.getXmlNode() != null) {
                    hasXml = true;
                    break;
                }
            }

            if (hasXml) {
                manager.add(new CopyCutAction(getEditor(), getClipboard(),
                        null, selected, true /* cut */));
                manager.add(new CopyCutAction(getEditor(), getClipboard(),
                        null, selected, false /* cut */));

                // Can't paste with more than one element selected (the selection is the target)
                if (selected.size() <= 1) {
                    // Paste is not valid if it would add a second element on a terminal element
                    // which parent is a document -- an XML document can only have one child. This
                    // means paste is valid if the current UI node can have children or if the
                    // parent is not a document.
                    UiElementNode ui_root = selected.get(0).getUiRoot();
                    if (ui_root.getDescriptor().hasChildren() ||
                            !(ui_root.getUiParent() instanceof UiDocumentNode)) {
                        manager.add(new PasteAction(getEditor(), getClipboard(), selected.get(0)));
                    }
                }
                manager.add(new Separator());
            }
        }

        // Append "add" and "remove" actions. They do the same thing as the add/remove
        // buttons on the side.
        Action action;
        IconFactory factory = IconFactory.getInstance();

        // "Add" makes sense only if there's 0 or 1 item selected since the
        // one selected item becomes the target.
        if (selected == null || selected.size() <= 1) {
            manager.add(new Action("Add...", factory.getImageDescriptor("add")) { //$NON-NLS-1$
                @Override
                public void run() {
                    super.run();
                    doTreeAdd();
                }
            });
        }

        if (selected != null) {
            if (selected != null) {
                manager.add(new Action("Remove", factory.getImageDescriptor("delete")) { //$NON-NLS-1$
                    @Override
                    public void run() {
                        super.run();
                        doTreeRemove();
                    }
                });
            }
            manager.add(new Separator());
            
            manager.add(new Action("Up", factory.getImageDescriptor("up")) { //$NON-NLS-1$
                @Override
                public void run() {
                    super.run();
                    doTreeUp();
                }
            });
            manager.add(new Action("Down", factory.getImageDescriptor("down")) { //$NON-NLS-1$
                @Override
                public void run() {
                    super.run();
                    doTreeDown();
                }
            });
        }
    
private voiddoTreeAdd()
Called when the "Add..." button next to the tree view is selected. Displays a selection dialog that lets the user select which kind of node to create, depending on the current selection.

        UiElementNode ui_node = mUiRootNode;
        ISelection selection = mTreeViewer.getSelection();
        if (!selection.isEmpty() && selection instanceof ITreeSelection) {
            ITreeSelection tree_selection = (ITreeSelection) selection;
            Object first = tree_selection.getFirstElement();
            if (first != null && first instanceof UiElementNode) {
                ui_node = (UiElementNode) first;
            }
        }

        mUiTreeActions.doAdd(
                ui_node,
                mDescriptorFilters,
                mTreeViewer.getControl().getShell(),
                (ILabelProvider) mTreeViewer.getLabelProvider());
    
protected voiddoTreeDown()
Called when the "Down" button is selected. If the tree has a selection, move it down, either in the same child list or as the first child of the next parent.

        ISelection selection = mTreeViewer.getSelection();
        if (!selection.isEmpty() && selection instanceof ITreeSelection) {
            ArrayList<UiElementNode> selected = filterSelection((ITreeSelection) selection);
            mUiTreeActions.doDown(selected);
        }
    
protected voiddoTreeRemove()
Called when the "Remove" button is selected. If the tree has a selection, remove it. This simply deletes the XML node attached to the UI node: when the XML model fires the update event, the tree will get refreshed.

        ISelection selection = mTreeViewer.getSelection();
        if (!selection.isEmpty() && selection instanceof ITreeSelection) {
            ArrayList<UiElementNode> selected = filterSelection((ITreeSelection) selection);
            mUiTreeActions.doRemove(selected, mTreeViewer.getControl().getShell());
        }
    
protected voiddoTreeUp()
Called when the "Up" button is selected.

If the tree has a selection, move it up, either in the child list or as the last child of the previous parent.

        ISelection selection = mTreeViewer.getSelection();
        if (!selection.isEmpty() && selection instanceof ITreeSelection) {
            ArrayList<UiElementNode> selected = filterSelection((ITreeSelection) selection);
            mUiTreeActions.doUp(selected);
        }
    
private java.util.ArrayListfilterSelection(org.eclipse.jface.viewers.ITreeSelection selection)
Filters an ITreeSelection to only keep the {@link UiElementNode}s (in case there's something else in there).

return
A new list of {@link UiElementNode} with at least one item or null.

        ArrayList<UiElementNode> selected = new ArrayList<UiElementNode>();
        
        for (Iterator it = selection.iterator(); it.hasNext(); ) {
            Object selectedObj = it.next();
        
            if (selectedObj instanceof UiElementNode) {
                selected.add((UiElementNode) selectedObj);
            }
        }

        return selected.size() > 0 ? selected : null;
    
org.eclipse.swt.dnd.ClipboardgetClipboard()

returns
The reference to the clipboard for copy-paste

        return mClipboard;
    
com.android.ide.eclipse.editors.AndroidEditorgetEditor()

returns
The container editor

        return mEditor;
    
com.android.ide.eclipse.editors.ui.SectionHelper.ManifestSectionPartgetMasterPart()

returns
The master-detail part, composed of a main tree and an auxiliary detail part

        return mMasterPart;
    
public com.android.ide.eclipse.editors.uimodel.UiElementNodegetRootNode()
Returns the {@link UiElementNode} for the current model.

This is used by the content provider attached to {@link #mTreeViewer} since the uiRootNode changes after each call to {@link #changeRootAndDescriptors(UiElementNode, ElementDescriptor[], boolean)}.

        return mUiRootNode;
    
protected voidregisterPages(org.eclipse.ui.forms.DetailsPart detailsPart)

        // Keep a reference on the details part (the super class doesn't provide a getter
        // for it.)
        mDetailsPart = detailsPart;
        
        // The page selection mechanism does not use pages registered by association with
        // a node class. Instead it uses a custom details page provider that provides a
        // new UiElementDetail instance for each node instance. A limit of 5 pages is
        // then set (the value is arbitrary but should be reasonable) for the internal
        // page book.
        detailsPart.setPageLimit(5);
        
        final UiTreeBlock tree = this;
        
        detailsPart.setPageProvider(new IDetailsPageProvider() {
            public IDetailsPage getPage(Object key) {
                if (key instanceof UiElementNode) {
                    return new UiElementDetail(tree);
                }
                return null;
            }

            public Object getPageKey(Object object) {
                return object;  // use node object as key
            }
        });