blob: 62abd5f682ad22b29312f3abc71bd778f6e457b6 [file] [log] [blame]
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.ide.eclipse.adt.internal.editors.layout;
import com.android.ide.eclipse.adt.internal.editors.IconFactory;
import com.android.ide.eclipse.adt.internal.editors.layout.parts.UiDocumentTreeEditPart;
import com.android.ide.eclipse.adt.internal.editors.layout.parts.UiElementTreeEditPart;
import com.android.ide.eclipse.adt.internal.editors.layout.parts.UiElementTreeEditPartFactory;
import com.android.ide.eclipse.adt.internal.editors.layout.parts.UiLayoutTreeEditPart;
import com.android.ide.eclipse.adt.internal.editors.layout.parts.UiViewTreeEditPart;
import com.android.ide.eclipse.adt.internal.editors.ui.tree.CopyCutAction;
import com.android.ide.eclipse.adt.internal.editors.ui.tree.PasteAction;
import com.android.ide.eclipse.adt.internal.editors.ui.tree.UiActions;
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.ui.EclipseUiHelper;
import org.eclipse.gef.EditPartViewer;
import org.eclipse.gef.ui.parts.ContentOutlinePage;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TreePath;
import org.eclipse.jface.viewers.TreeSelection;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeItem;
import org.eclipse.ui.IActionBars;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
/**
* Implementation of the {@link ContentOutlinePage} to display {@link UiElementNode}.
*/
class UiContentOutlinePage extends ContentOutlinePage {
private AbstractGraphicalLayoutEditor mEditor;
private Action mAddAction;
private Action mDeleteAction;
private Action mUpAction;
private Action mDownAction;
private UiOutlineActions mUiActions = new UiOutlineActions();
public UiContentOutlinePage(AbstractGraphicalLayoutEditor editor, final EditPartViewer viewer) {
super(viewer);
mEditor = editor;
IconFactory factory = IconFactory.getInstance();
mAddAction = new Action("Add...") {
@Override
public void run() {
List<UiElementNode> nodes = getModelSelections();
UiElementNode node = nodes != null && nodes.size() > 0 ? nodes.get(0) : null;
mUiActions.doAdd(node, viewer.getControl().getShell());
}
};
mAddAction.setToolTipText("Adds a new element.");
mAddAction.setImageDescriptor(factory.getImageDescriptor("add")); //$NON-NLS-1$
mDeleteAction = new Action("Remove...") {
@Override
public void run() {
List<UiElementNode> nodes = getModelSelections();
mUiActions.doRemove(nodes, viewer.getControl().getShell());
}
};
mDeleteAction.setToolTipText("Removes an existing selected element.");
mDeleteAction.setImageDescriptor(factory.getImageDescriptor("delete")); //$NON-NLS-1$
mUpAction = new Action("Up") {
@Override
public void run() {
List<UiElementNode> nodes = getModelSelections();
mUiActions.doUp(nodes);
}
};
mUpAction.setToolTipText("Moves the selected element up");
mUpAction.setImageDescriptor(factory.getImageDescriptor("up")); //$NON-NLS-1$
mDownAction = new Action("Down") {
@Override
public void run() {
List<UiElementNode> nodes = getModelSelections();
mUiActions.doDown(nodes);
}
};
mDownAction.setToolTipText("Moves the selected element down");
mDownAction.setImageDescriptor(factory.getImageDescriptor("down")); //$NON-NLS-1$
// all actions disabled by default.
mAddAction.setEnabled(false);
mDeleteAction.setEnabled(false);
mUpAction.setEnabled(false);
mDownAction.setEnabled(false);
addSelectionChangedListener(new ISelectionChangedListener() {
public void selectionChanged(SelectionChangedEvent event) {
ISelection selection = event.getSelection();
// the selection is never empty. The least it'll contain is the
// UiDocumentTreeEditPart object.
if (selection instanceof StructuredSelection) {
StructuredSelection structSel = (StructuredSelection)selection;
if (structSel.size() == 1 &&
structSel.getFirstElement() instanceof UiDocumentTreeEditPart) {
mDeleteAction.setEnabled(false);
mUpAction.setEnabled(false);
mDownAction.setEnabled(false);
} else {
mDeleteAction.setEnabled(true);
mUpAction.setEnabled(true);
mDownAction.setEnabled(true);
}
// the "add" button is always enabled, in order to be able to set the
// initial root node
mAddAction.setEnabled(true);
}
}
});
}
/* (non-Javadoc)
* @see org.eclipse.ui.part.IPage#createControl(org.eclipse.swt.widgets.Composite)
*/
@Override
public void createControl(Composite parent) {
// create outline viewer page
getViewer().createControl(parent);
// configure outline viewer
getViewer().setEditPartFactory(new UiElementTreeEditPartFactory());
setupOutline();
setupContextMenu();
setupTooltip();
setupDoubleClick();
}
/*
* (non-Javadoc)
* @see org.eclipse.ui.part.Page#setActionBars(org.eclipse.ui.IActionBars)
*
* Called automatically after createControl
*/
@Override
public void setActionBars(IActionBars actionBars) {
IToolBarManager toolBarManager = actionBars.getToolBarManager();
toolBarManager.add(mAddAction);
toolBarManager.add(mDeleteAction);
toolBarManager.add(new Separator());
toolBarManager.add(mUpAction);
toolBarManager.add(mDownAction);
IMenuManager menuManager = actionBars.getMenuManager();
menuManager.add(mAddAction);
menuManager.add(mDeleteAction);
menuManager.add(new Separator());
menuManager.add(mUpAction);
menuManager.add(mDownAction);
}
/* (non-Javadoc)
* @see org.eclipse.ui.part.IPage#dispose()
*/
@Override
public void dispose() {
breakConnectionWithEditor();
// dispose
super.dispose();
}
/* (non-Javadoc)
* @see org.eclipse.ui.part.IPage#getControl()
*/
@Override
public Control getControl() {
return getViewer().getControl();
}
void setNewEditor(GraphicalLayoutEditor editor) {
mEditor = editor;
setupOutline();
}
void breakConnectionWithEditor() {
// unhook outline viewer
mEditor.getSelectionSynchronizer().removeViewer(getViewer());
}
private void setupOutline() {
getViewer().setEditDomain(mEditor.getEditDomain());
// hook outline viewer
mEditor.getSelectionSynchronizer().addViewer(getViewer());
// initialize outline viewer with model
getViewer().setContents(mEditor.getModel());
}
private void setupContextMenu() {
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) {
List<UiElementNode> selected = getModelSelections();
if (selected != null) {
doCreateMenuAction(manager, selected);
return;
}
doCreateMenuAction(manager, null /* ui_node */);
}
});
Control control = getControl();
Menu contextMenu = menuManager.createContextMenu(control);
control.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 node selected in the tree. Can be null, in which case the root
* is to be modified.
*/
private void doCreateMenuAction(IMenuManager manager, List<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(mEditor.getLayoutEditor(), mEditor.getClipboard(),
null, selected, true /* cut */));
manager.add(new CopyCutAction(mEditor.getLayoutEditor(), mEditor.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(mEditor.getLayoutEditor(),
mEditor.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.
//
// "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(mAddAction);
}
if (selected != null) {
manager.add(mDeleteAction);
manager.add(new Separator());
manager.add(mUpAction);
manager.add(mDownAction);
}
if (selected != null && selected.size() == 1) {
manager.add(new Separator());
Action propertiesAction = new Action("Properties") {
@Override
public void run() {
EclipseUiHelper.showView(EclipseUiHelper.PROPERTY_SHEET_VIEW_ID,
true /* activate */);
}
};
propertiesAction.setToolTipText("Displays properties of the selected element.");
manager.add(propertiesAction);
}
}
/**
* Updates the outline view with the model of the {@link GraphicalLayoutEditor}.
* <p/>
* This attemps to preserve the selection, if any.
*/
public void reloadModel() {
// Attemps to preserve the UiNode selection, if any
List<UiElementNode> uiNodes = null;
try {
// get current selection using the model rather than the edit part as
// reloading the content may change the actual edit part.
uiNodes = getModelSelections();
// perform the update
getViewer().setContents(mEditor.getModel());
} finally {
// restore selection
if (uiNodes != null) {
setModelSelection(uiNodes.get(0));
}
}
}
/**
* Returns the currently selected element, if any, in the viewer.
* This returns the viewer's elements (i.e. an {@link UiElementTreeEditPart})
* and not the underlying model node.
* <p/>
* When there is no actual selection, this might still return the root node,
* which is of type {@link UiDocumentTreeEditPart}.
*/
@SuppressWarnings("unchecked")
private List<UiElementTreeEditPart> getViewerSelections() {
ISelection selection = getSelection();
if (selection instanceof StructuredSelection) {
StructuredSelection structuredSelection = (StructuredSelection)selection;
if (structuredSelection.size() > 0) {
ArrayList<UiElementTreeEditPart> selected = new ArrayList<UiElementTreeEditPart>();
for (Iterator it = structuredSelection.iterator(); it.hasNext(); ) {
Object selectedObj = it.next();
if (selectedObj instanceof UiElementTreeEditPart) {
selected.add((UiElementTreeEditPart) selectedObj);
}
}
return selected.size() > 0 ? selected : null;
}
}
return null;
}
/**
* Returns the currently selected model element, which is either an
* {@link UiViewTreeEditPart} or an {@link UiLayoutTreeEditPart}.
* <p/>
* Returns null if there is no selection or if the implicit root is "selected"
* (which actually represents the lack of a real element selection.)
*/
private List<UiElementNode> getModelSelections() {
List<UiElementTreeEditPart> parts = getViewerSelections();
if (parts != null) {
ArrayList<UiElementNode> selected = new ArrayList<UiElementNode>();
for (UiElementTreeEditPart part : parts) {
if (part instanceof UiViewTreeEditPart || part instanceof UiLayoutTreeEditPart) {
selected.add((UiElementNode) part.getModel());
}
}
return selected.size() > 0 ? selected : null;
}
return null;
}
/**
* Selects the corresponding edit part in the tree viewer.
*/
private void setViewerSelection(UiElementTreeEditPart selectedPart) {
if (selectedPart != null && !(selectedPart instanceof UiDocumentTreeEditPart)) {
LinkedList<UiElementTreeEditPart> segments = new LinkedList<UiElementTreeEditPart>();
for (UiElementTreeEditPart part = selectedPart;
!(part instanceof UiDocumentTreeEditPart);
part = (UiElementTreeEditPart) part.getParent()) {
segments.add(0, part);
}
setSelection(new TreeSelection(new TreePath(segments.toArray())));
}
}
/**
* Selects the corresponding model element in the tree viewer.
*/
private void setModelSelection(UiElementNode uiNodeToSelect) {
if (uiNodeToSelect != null) {
// find an edit part that has the requested model element
UiElementTreeEditPart part = findPartForModel(
(UiElementTreeEditPart) getViewer().getContents(),
uiNodeToSelect);
// if we found a part, select it and reveal it
if (part != null) {
setViewerSelection(part);
getViewer().reveal(part);
}
}
}
/**
* Utility method that tries to find an edit part that matches a given model UI node.
*
* @param rootPart The root of the viewer edit parts
* @param uiNode The UI node model to find
* @return The part that matches the model or null if it's not in the sub tree.
*/
private UiElementTreeEditPart findPartForModel(UiElementTreeEditPart rootPart,
UiElementNode uiNode) {
if (rootPart.getModel() == uiNode) {
return rootPart;
}
for (Object part : rootPart.getChildren()) {
if (part instanceof UiElementTreeEditPart) {
UiElementTreeEditPart found = findPartForModel(
(UiElementTreeEditPart) part, uiNode);
if (found != null) {
return found;
}
}
}
return null;
}
/**
* Sets up a custom tooltip when hovering over tree items.
* <p/>
* The tooltip will display the element's javadoc, if any, or the item's getText otherwise.
*/
private void setupTooltip() {
final Tree tree = (Tree) getControl();
/*
* Reference:
* http://dev.eclipse.org/viewcvs/index.cgi/org.eclipse.swt.snippets/src/org/eclipse/swt/snippets/Snippet125.java?view=markup
*/
final Listener listener = new Listener() {
Shell tip = null;
Label label = null;
public void handleEvent(Event event) {
switch(event.type) {
case SWT.Dispose:
case SWT.KeyDown:
case SWT.MouseExit:
case SWT.MouseDown:
case SWT.MouseMove:
if (tip != null) {
tip.dispose();
tip = null;
label = null;
}
break;
case SWT.MouseHover:
if (tip != null) {
tip.dispose();
tip = null;
label = null;
}
String tooltip = null;
TreeItem item = tree.getItem(new Point(event.x, event.y));
if (item != null) {
Object data = item.getData();
if (data instanceof UiElementTreeEditPart) {
Object model = ((UiElementTreeEditPart) data).getModel();
if (model instanceof UiElementNode) {
tooltip = ((UiElementNode) model).getDescriptor().getTooltip();
}
}
if (tooltip == null) {
tooltip = item.getText();
} else {
tooltip = item.getText() + ":\r" + tooltip;
}
}
if (tooltip != null) {
Shell shell = tree.getShell();
Display display = tree.getDisplay();
tip = new Shell(shell, SWT.ON_TOP | SWT.NO_FOCUS | SWT.TOOL);
tip.setBackground(display .getSystemColor(SWT.COLOR_INFO_BACKGROUND));
FillLayout layout = new FillLayout();
layout.marginWidth = 2;
tip.setLayout(layout);
label = new Label(tip, SWT.NONE);
label.setForeground(display.getSystemColor(SWT.COLOR_INFO_FOREGROUND));
label.setBackground(display.getSystemColor(SWT.COLOR_INFO_BACKGROUND));
label.setData("_TABLEITEM", item);
label.setText(tooltip);
label.addListener(SWT.MouseExit, this);
label.addListener(SWT.MouseDown, this);
Point size = tip.computeSize(SWT.DEFAULT, SWT.DEFAULT);
Rectangle rect = item.getBounds(0);
Point pt = tree.toDisplay(rect.x, rect.y);
tip.setBounds(pt.x, pt.y, size.x, size.y);
tip.setVisible(true);
}
}
}
};
tree.addListener(SWT.Dispose, listener);
tree.addListener(SWT.KeyDown, listener);
tree.addListener(SWT.MouseMove, listener);
tree.addListener(SWT.MouseHover, listener);
}
/**
* Sets up double-click action on the tree.
* <p/>
* By default, double-click (a.k.a. "default selection") on a valid list item will
* show the property view.
*/
private void setupDoubleClick() {
final Tree tree = (Tree) getControl();
tree.addListener(SWT.DefaultSelection, new Listener() {
public void handleEvent(Event event) {
EclipseUiHelper.showView(EclipseUiHelper.PROPERTY_SHEET_VIEW_ID,
true /* activate */);
}
});
}
// ---------------
private class UiOutlineActions extends UiActions {
@Override
protected UiDocumentNode getRootNode() {
return mEditor.getModel(); // this is LayoutEditor.getUiRootNode()
}
// Select the new item
@Override
protected void selectUiNode(UiElementNode uiNodeToSelect) {
setModelSelection(uiNodeToSelect);
}
@Override
public void commitPendingXmlChanges() {
// Pass. There is nothing to commit before the XML is changed here.
}
}
}