blob: d11b8a4c63f6b9b4b3180e027ae10718856ed4bc [file] [log] [blame]
/*
* Copyright (C) 2007 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.ui.tree;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
import com.android.ide.eclipse.adt.internal.editors.IconFactory;
import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor;
import com.android.ide.eclipse.adt.internal.editors.ui.SectionHelper;
import com.android.ide.eclipse.adt.internal.editors.ui.SectionHelper.ManifestSectionPart;
import com.android.ide.eclipse.adt.internal.editors.uimodel.IUiUpdateListener;
import com.android.ide.eclipse.adt.internal.editors.uimodel.UiDocumentNode;
import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
import com.android.ide.eclipse.adt.internal.sdk.Sdk.ITargetChangeListener;
import com.android.ide.eclipse.adt.internal.sdk.Sdk.TargetChangeListener;
import org.eclipse.core.resources.IProject;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.action.ToolBarManager;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
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.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerComparator;
import org.eclipse.jface.viewers.ViewerFilter;
import org.eclipse.swt.SWT;
import org.eclipse.swt.dnd.Clipboard;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.ToolBar;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.ui.forms.DetailsPart;
import org.eclipse.ui.forms.IDetailsPage;
import org.eclipse.ui.forms.IDetailsPageProvider;
import org.eclipse.ui.forms.IManagedForm;
import org.eclipse.ui.forms.MasterDetailsBlock;
import org.eclipse.ui.forms.widgets.FormToolkit;
import org.eclipse.ui.forms.widgets.Section;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
/**
* {@link UiTreeBlock} is a {@link MasterDetailsBlock} which displays a tree view for
* a specific set of {@link UiElementNode}.
* <p/>
* 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.
* <p/>
* 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.
*/
public final class UiTreeBlock extends MasterDetailsBlock implements ICommitXml {
/** Height hint for the tree view. Helps the grid layout resize properly on smaller screens. */
private static final int TREE_HEIGHT_HINT = 50;
/** Container editor */
AndroidXmlEditor mEditor;
/** 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 UiElementNode mUiRootNode;
/** 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. Can be null or empty to mean everything
* can be displayed. */
private ElementDescriptor[] mDescriptorFilters;
/** The title for the master-detail part (displayed on the top "tab" on top of the tree) */
private String mTitle;
/** The description for the master-detail part (displayed on top of the tree view) */
private String mDescription;
/** The master-detail part, composed of a main tree and an auxiliary detail part */
private ManifestSectionPart mMasterPart;
/** The tree viewer in the master-detail part */
private TreeViewer mTreeViewer;
/** The "add" button for the tree view */
private Button mAddButton;
/** The "remove" button for the tree view */
private Button mRemoveButton;
/** The "up" button for the tree view */
private Button mUpButton;
/** The "down" button for the tree view */
private Button mDownButton;
/** The Managed Form used to create the master part */
private IManagedForm mManagedForm;
/** Reference to the details part of the tree master block. */
private DetailsPart mDetailsPart;
/** Reference to the clipboard for copy-paste */
private Clipboard mClipboard;
/** Listener to refresh the tree viewer when the parent's node has been updated */
private IUiUpdateListener mUiRefreshListener;
/** Listener to enable/disable the UI based on the application node's presence */
private IUiUpdateListener mUiEnableListener;
/** An adapter/wrapper to use the add/remove/up/down tree edit actions. */
private UiTreeActions mUiTreeActions;
/**
* 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.
*/
private final boolean mAutoCreateRoot;
/**
* 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
*/
public UiTreeBlock(AndroidXmlEditor editor,
UiElementNode uiRootNode,
boolean autoCreateRoot,
ElementDescriptor[] descriptorFilters,
String title,
String description) {
mEditor = editor;
mUiRootNode = uiRootNode;
mAutoCreateRoot = autoCreateRoot;
mDescriptorFilters = descriptorFilters;
mTitle = title;
mDescription = description;
}
/** @returns The container editor */
AndroidXmlEditor getEditor() {
return mEditor;
}
/** @returns The reference to the clipboard for copy-paste */
Clipboard getClipboard() {
return mClipboard;
}
/** @returns The master-detail part, composed of a main tree and an auxiliary detail part */
ManifestSectionPart getMasterPart() {
return mMasterPart;
}
/**
* Returns the {@link UiElementNode} for the current model.
* <p/>
* This is used by the content provider attached to {@link #mTreeViewer} since
* the uiRootNode changes after each call to
* {@link #changeRootAndDescriptors(UiElementNode, ElementDescriptor[], boolean)}.
*/
public UiElementNode getRootNode() {
return mUiRootNode;
}
@Override
protected void createMasterPart(final IManagedForm managedForm, 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 void createSectionActions(Section section, 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*/);
}
/**
* Creates the tree and its viewer
* @return The tree control
*/
private Tree createTreeViewer(FormToolkit toolkit, Composite grid,
final IManagedForm managedForm) {
// 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 = AndroidXmlEditor.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() {
@Override
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() {
@Override
public void uiElementNodeUpdated(UiElementNode ui_node, UiUpdateState state) {
mTreeViewer.refresh();
}
};
mUiEnableListener = new IUiUpdateListener() {
@Override
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 TargetChangeListener() {
@Override
public IProject getProject() {
if (mEditor != null) {
return mEditor.getProject();
}
return null;
}
@Override
public void reload() {
// 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() {
@Override
public void widgetDisposed(DisposeEvent e) {
if (mUiRootNode != null) {
UiElementNode node = mUiRootNode.getUiParent() != null ?
mUiRootNode.getUiParent() :
mUiRootNode;
if (node != null) {
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;
}
/**
* Changes the UI root node and the descriptor filters of the tree.
* <p/>
* 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
*/
public void changeRootAndDescriptors(UiElementNode uiRootNode,
ElementDescriptor[] descriptorFilters, boolean forceRefresh) {
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.
if (mUiRootNode != null) {
node = mUiRootNode.getUiParent() != null ? mUiRootNode.getUiParent() : mUiRootNode;
if (node != null) {
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());
}
/**
* Creates the buttons next to the tree.
*/
private void createButtons(FormToolkit toolkit, Composite grid) {
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);
}
private void createTreeContextMenu(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.
*/
@Override
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);
}
/**
* 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.
*/
private void doCreateMenuAction(IMenuManager manager, ArrayList<UiElementNode> selected) {
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.
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();
}
});
}
}
/**
* 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())
*/
private void adjustTreeButtons(ISelection selection) {
mRemoveButton.setEnabled(!selection.isEmpty() && selection instanceof ITreeSelection);
mUpButton.setEnabled(canDoTreeUp(selection));
mDownButton.setEnabled(canDoTreeDown(selection));
}
/**
* An adapter/wrapper to use the add/remove/up/down tree edit actions.
*/
private class UiTreeActions extends UiActions {
@Override
protected UiElementNode getRootNode() {
return mUiRootNode;
}
@Override
protected void selectUiNode(UiElementNode uiNodeToSelect) {
// Select the new item
if (uiNodeToSelect != null) {
LinkedList<UiElementNode> segments = new LinkedList<UiElementNode>();
for (UiElementNode ui_node = uiNodeToSelect; ui_node != mUiRootNode;
ui_node = ui_node.getUiParent()) {
segments.add(0, ui_node);
}
if (segments.size() > 0) {
mTreeViewer.setSelection(new TreeSelection(new TreePath(segments.toArray())));
} else {
mTreeViewer.setSelection(null);
}
}
}
@Override
public void commitPendingXmlChanges() {
commitManagedForm();
}
}
/**
* 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.
*/
private ArrayList<UiElementNode> filterSelection(ITreeSelection selection) {
ArrayList<UiElementNode> selected = new ArrayList<UiElementNode>();
for (Iterator<Object> it = selection.iterator(); it.hasNext(); ) {
Object selectedObj = it.next();
if (selectedObj instanceof UiElementNode) {
selected.add((UiElementNode) selectedObj);
}
}
return selected.size() > 0 ? selected : null;
}
/**
* 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.
*/
private void doTreeAdd() {
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());
}
/**
* 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.
*/
protected void doTreeRemove() {
ISelection selection = mTreeViewer.getSelection();
if (!selection.isEmpty() && selection instanceof ITreeSelection) {
ArrayList<UiElementNode> selected = filterSelection((ITreeSelection) selection);
mUiTreeActions.doRemove(selected, mTreeViewer.getControl().getShell());
}
}
/**
* Called when the "Up" button is selected.
* <p/>
* If the tree has a selection, move it up, either in the child list or as the last child
* of the previous parent.
*/
protected void doTreeUp() {
ISelection selection = mTreeViewer.getSelection();
if (!selection.isEmpty() && selection instanceof ITreeSelection) {
ArrayList<UiElementNode> selected = filterSelection((ITreeSelection) selection);
mUiTreeActions.doUp(selected, mDescriptorFilters);
}
}
/**
* Checks whether the "up" action can be done on the current selection.
*
* @param selection The current tree selection.
* @return True if all the selected nodes can be moved up.
*/
protected boolean canDoTreeUp(ISelection selection) {
if (!selection.isEmpty() && selection instanceof ITreeSelection) {
ArrayList<UiElementNode> selected = filterSelection((ITreeSelection) selection);
return mUiTreeActions.canDoUp(selected, mDescriptorFilters);
}
return false;
}
/**
* 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.
*/
protected void doTreeDown() {
ISelection selection = mTreeViewer.getSelection();
if (!selection.isEmpty() && selection instanceof ITreeSelection) {
ArrayList<UiElementNode> selected = filterSelection((ITreeSelection) selection);
mUiTreeActions.doDown(selected, mDescriptorFilters);
}
}
/**
* Checks whether the "down" action can be done on the current selection.
*
* @param selection The current tree selection.
* @return True if all the selected nodes can be moved down.
*/
protected boolean canDoTreeDown(ISelection selection) {
if (!selection.isEmpty() && selection instanceof ITreeSelection) {
ArrayList<UiElementNode> selected = filterSelection((ITreeSelection) selection);
return mUiTreeActions.canDoDown(selected, mDescriptorFilters);
}
return false;
}
/**
* Commits the current managed form (the one associated with our master part).
* As a side effect, this will commit the current UiElementDetails page.
*/
void commitManagedForm() {
if (mManagedForm != null) {
mManagedForm.commit(false /* onSave */);
}
}
/* Implements ICommitXml for CopyCutAction */
@Override
public void commitPendingXmlChanges() {
commitManagedForm();
}
@Override
protected void createToolBarActions(IManagedForm managedForm) {
// Pass. Not used, toolbar actions are defined by createSectionActions().
}
@Override
protected void registerPages(DetailsPart inDetailsPart) {
// Keep a reference on the details part (the super class doesn't provide a getter
// for it.)
mDetailsPart = inDetailsPart;
// 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.
inDetailsPart.setPageLimit(5);
final UiTreeBlock tree = this;
inDetailsPart.setPageProvider(new IDetailsPageProvider() {
@Override
public IDetailsPage getPage(Object key) {
if (key instanceof UiElementNode) {
return new UiElementDetail(tree);
}
return null;
}
@Override
public Object getPageKey(Object object) {
return object; // use node object as key
}
});
}
/**
* An alphabetic sort action for the tree viewer.
*/
private class TreeSortAction extends Action {
private ViewerComparator mComparator;
public TreeSortAction() {
super("Sorts elements alphabetically.", AS_CHECK_BOX);
setImageDescriptor(IconFactory.getInstance().getImageDescriptor("az_sort")); //$NON-NLS-1$
if (mTreeViewer != null) {
boolean is_sorted = mTreeViewer.getComparator() != null;
setChecked(is_sorted);
}
}
/**
* Called when the button is selected. Toggles the tree viewer comparator.
*/
@Override
public void run() {
if (mTreeViewer == null) {
notifyResult(false /*success*/);
return;
}
ViewerComparator comp = mTreeViewer.getComparator();
if (comp != null) {
// Tree is currently sorted.
// Save currently comparator and remove it
mComparator = comp;
mTreeViewer.setComparator(null);
} else {
// Tree is not currently sorted.
// Reuse or add a new comparator.
if (mComparator == null) {
mComparator = new ViewerComparator();
}
mTreeViewer.setComparator(mComparator);
}
notifyResult(true /*success*/);
}
}
/**
* A filter on descriptor for the tree viewer.
* <p/>
* The tree viewer will contain many of these actions and only one can be enabled at a
* given time. When no action is selected, everything is displayed.
* <p/>
* Since "radio"-like actions do not allow for unselecting all of them, we manually
* handle the exclusive radio button-like property: when an action is selected, it manually
* removes all other actions as needed.
*/
private class DescriptorFilterAction extends Action {
private final ElementDescriptor mDescriptor;
private ViewerFilter mFilter;
public DescriptorFilterAction(ElementDescriptor descriptor) {
super(String.format("Displays only %1$s elements.", descriptor.getUiName()),
AS_CHECK_BOX);
mDescriptor = descriptor;
setImageDescriptor(descriptor.getImageDescriptor());
}
/**
* Called when the button is selected.
* <p/>
* Find any existing {@link DescriptorFilter}s and remove them. Install ours.
*/
@Override
public void run() {
super.run();
if (isChecked()) {
if (mFilter == null) {
// create filter when required
mFilter = new DescriptorFilter(this);
}
// we add our filter first, otherwise the UI might show the full list
mTreeViewer.addFilter(mFilter);
// Then remove the any other filters except ours. There should be at most
// one other filter, since that's how the actions are made to look like
// exclusive radio buttons.
for (ViewerFilter filter : mTreeViewer.getFilters()) {
if (filter instanceof DescriptorFilter && filter != mFilter) {
DescriptorFilterAction action = ((DescriptorFilter) filter).getAction();
action.setChecked(false);
mTreeViewer.removeFilter(filter);
}
}
} else if (mFilter != null){
mTreeViewer.removeFilter(mFilter);
}
}
/**
* Filters the tree viewer for the given descriptor.
* <p/>
* The filter is linked to the action so that an action can iterate through the list
* of filters and un-select the actions.
*/
private class DescriptorFilter extends ViewerFilter {
private final DescriptorFilterAction mAction;
public DescriptorFilter(DescriptorFilterAction action) {
mAction = action;
}
public DescriptorFilterAction getAction() {
return mAction;
}
/**
* Returns true if an element should be displayed, that if the element or
* any of its parent matches the requested descriptor.
*/
@Override
public boolean select(Viewer viewer, Object parentElement, Object element) {
while (element instanceof UiElementNode) {
UiElementNode uiNode = (UiElementNode)element;
if (uiNode.getDescriptor() == mDescriptor) {
return true;
}
element = uiNode.getUiParent();
}
return false;
}
}
}
}