aboutsummaryrefslogtreecommitdiff
path: root/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/ui/tree/UiTreeBlock.java
diff options
context:
space:
mode:
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/ui/tree/UiTreeBlock.java')
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/ui/tree/UiTreeBlock.java946
1 files changed, 946 insertions, 0 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/ui/tree/UiTreeBlock.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/ui/tree/UiTreeBlock.java
new file mode 100644
index 000000000..d11b8a4c6
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/ui/tree/UiTreeBlock.java
@@ -0,0 +1,946 @@
+/*
+ * 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;
+ }
+ }
+ }
+
+}