2 * Copyright (C) 2008 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
18 package com.android.ide.eclipse.adt.internal.editors.layout.gle1;
20 import com.android.ide.eclipse.adt.internal.editors.IconFactory;
21 import com.android.ide.eclipse.adt.internal.editors.layout.IGraphicalLayoutEditor;
22 import com.android.ide.eclipse.adt.internal.editors.layout.parts.UiDocumentTreeEditPart;
23 import com.android.ide.eclipse.adt.internal.editors.layout.parts.UiElementTreeEditPart;
24 import com.android.ide.eclipse.adt.internal.editors.layout.parts.UiElementTreeEditPartFactory;
25 import com.android.ide.eclipse.adt.internal.editors.layout.parts.UiLayoutTreeEditPart;
26 import com.android.ide.eclipse.adt.internal.editors.layout.parts.UiViewTreeEditPart;
27 import com.android.ide.eclipse.adt.internal.editors.ui.tree.CopyCutAction;
28 import com.android.ide.eclipse.adt.internal.editors.ui.tree.PasteAction;
29 import com.android.ide.eclipse.adt.internal.editors.ui.tree.UiActions;
30 import com.android.ide.eclipse.adt.internal.editors.uimodel.UiDocumentNode;
31 import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
32 import com.android.ide.eclipse.adt.internal.ui.EclipseUiHelper;
34 import org.eclipse.gef.EditPartViewer;
35 import org.eclipse.gef.ui.parts.ContentOutlinePage;
36 import org.eclipse.jface.action.Action;
37 import org.eclipse.jface.action.IMenuListener;
38 import org.eclipse.jface.action.IMenuManager;
39 import org.eclipse.jface.action.IToolBarManager;
40 import org.eclipse.jface.action.MenuManager;
41 import org.eclipse.jface.action.Separator;
42 import org.eclipse.jface.viewers.ISelection;
43 import org.eclipse.jface.viewers.ISelectionChangedListener;
44 import org.eclipse.jface.viewers.SelectionChangedEvent;
45 import org.eclipse.jface.viewers.StructuredSelection;
46 import org.eclipse.jface.viewers.TreePath;
47 import org.eclipse.jface.viewers.TreeSelection;
48 import org.eclipse.swt.SWT;
49 import org.eclipse.swt.graphics.Point;
50 import org.eclipse.swt.graphics.Rectangle;
51 import org.eclipse.swt.layout.FillLayout;
52 import org.eclipse.swt.widgets.Composite;
53 import org.eclipse.swt.widgets.Control;
54 import org.eclipse.swt.widgets.Display;
55 import org.eclipse.swt.widgets.Event;
56 import org.eclipse.swt.widgets.Label;
57 import org.eclipse.swt.widgets.Listener;
58 import org.eclipse.swt.widgets.Menu;
59 import org.eclipse.swt.widgets.Shell;
60 import org.eclipse.swt.widgets.Tree;
61 import org.eclipse.swt.widgets.TreeItem;
62 import org.eclipse.ui.IActionBars;
64 import java.util.ArrayList;
65 import java.util.Iterator;
66 import java.util.LinkedList;
67 import java.util.List;
70 * Implementation of the {@link ContentOutlinePage} to display {@link UiElementNode}.
74 public class UiContentOutlinePage extends ContentOutlinePage {
76 private GraphicalLayoutEditor mEditor;
78 private Action mAddAction;
79 private Action mDeleteAction;
80 private Action mUpAction;
81 private Action mDownAction;
83 private UiOutlineActions mUiActions = new UiOutlineActions();
85 public UiContentOutlinePage(GraphicalLayoutEditor editor, final EditPartViewer viewer) {
88 IconFactory factory = IconFactory.getInstance();
90 mAddAction = new Action("Add...") {
93 List<UiElementNode> nodes = getModelSelections();
94 UiElementNode node = nodes != null && nodes.size() > 0 ? nodes.get(0) : null;
96 mUiActions.doAdd(node, viewer.getControl().getShell());
99 mAddAction.setToolTipText("Adds a new element.");
100 mAddAction.setImageDescriptor(factory.getImageDescriptor("add")); //$NON-NLS-1$
102 mDeleteAction = new Action("Remove...") {
105 List<UiElementNode> nodes = getModelSelections();
107 mUiActions.doRemove(nodes, viewer.getControl().getShell());
110 mDeleteAction.setToolTipText("Removes an existing selected element.");
111 mDeleteAction.setImageDescriptor(factory.getImageDescriptor("delete")); //$NON-NLS-1$
113 mUpAction = new Action("Up") {
116 List<UiElementNode> nodes = getModelSelections();
118 mUiActions.doUp(nodes);
121 mUpAction.setToolTipText("Moves the selected element up");
122 mUpAction.setImageDescriptor(factory.getImageDescriptor("up")); //$NON-NLS-1$
124 mDownAction = new Action("Down") {
127 List<UiElementNode> nodes = getModelSelections();
129 mUiActions.doDown(nodes);
132 mDownAction.setToolTipText("Moves the selected element down");
133 mDownAction.setImageDescriptor(factory.getImageDescriptor("down")); //$NON-NLS-1$
135 // all actions disabled by default.
136 mAddAction.setEnabled(false);
137 mDeleteAction.setEnabled(false);
138 mUpAction.setEnabled(false);
139 mDownAction.setEnabled(false);
141 addSelectionChangedListener(new ISelectionChangedListener() {
142 public void selectionChanged(SelectionChangedEvent event) {
143 ISelection selection = event.getSelection();
145 // the selection is never empty. The least it'll contain is the
146 // UiDocumentTreeEditPart object.
147 if (selection instanceof StructuredSelection) {
148 StructuredSelection structSel = (StructuredSelection)selection;
150 if (structSel.size() == 1 &&
151 structSel.getFirstElement() instanceof UiDocumentTreeEditPart) {
152 mDeleteAction.setEnabled(false);
153 mUpAction.setEnabled(false);
154 mDownAction.setEnabled(false);
156 mDeleteAction.setEnabled(true);
157 mUpAction.setEnabled(true);
158 mDownAction.setEnabled(true);
161 // the "add" button is always enabled, in order to be able to set the
163 mAddAction.setEnabled(true);
171 * @see org.eclipse.ui.part.IPage#createControl(org.eclipse.swt.widgets.Composite)
174 public void createControl(Composite parent) {
175 // create outline viewer page
176 getViewer().createControl(parent);
178 // configure outline viewer
179 getViewer().setEditPartFactory(new UiElementTreeEditPartFactory());
189 * @see org.eclipse.ui.part.Page#setActionBars(org.eclipse.ui.IActionBars)
191 * Called automatically after createControl
194 public void setActionBars(IActionBars actionBars) {
195 IToolBarManager toolBarManager = actionBars.getToolBarManager();
196 toolBarManager.add(mAddAction);
197 toolBarManager.add(mDeleteAction);
198 toolBarManager.add(new Separator());
199 toolBarManager.add(mUpAction);
200 toolBarManager.add(mDownAction);
202 IMenuManager menuManager = actionBars.getMenuManager();
203 menuManager.add(mAddAction);
204 menuManager.add(mDeleteAction);
205 menuManager.add(new Separator());
206 menuManager.add(mUpAction);
207 menuManager.add(mDownAction);
211 * @see org.eclipse.ui.part.IPage#dispose()
214 public void dispose() {
215 breakConnectionWithEditor();
222 * @see org.eclipse.ui.part.IPage#getControl()
225 public Control getControl() {
226 return getViewer().getControl();
229 void setNewEditor(GraphicalLayoutEditor editor) {
234 void breakConnectionWithEditor() {
235 // unhook outline viewer
236 mEditor.getSelectionSynchronizer().removeViewer(getViewer());
239 private void setupOutline() {
241 getViewer().setEditDomain(mEditor.getEditDomain());
243 // hook outline viewer
244 mEditor.getSelectionSynchronizer().addViewer(getViewer());
246 // initialize outline viewer with model
247 getViewer().setContents(mEditor.getModel());
250 private void setupContextMenu() {
251 MenuManager menuManager = new MenuManager();
252 menuManager.setRemoveAllWhenShown(true);
253 menuManager.addMenuListener(new IMenuListener() {
255 * The menu is about to be shown. The menu manager has already been
256 * requested to remove any existing menu item. This method gets the
257 * tree selection and if it is of the appropriate type it re-creates
258 * the necessary actions.
260 public void menuAboutToShow(IMenuManager manager) {
261 List<UiElementNode> selected = getModelSelections();
263 if (selected != null) {
264 doCreateMenuAction(manager, selected);
267 doCreateMenuAction(manager, null /* ui_node */);
270 Control control = getControl();
271 Menu contextMenu = menuManager.createContextMenu(control);
272 control.setMenu(contextMenu);
276 * Adds the menu actions to the context menu when the given UI node is selected in
279 * @param manager The context menu manager
280 * @param selected The UI node selected in the tree. Can be null, in which case the root
283 private void doCreateMenuAction(IMenuManager manager, List<UiElementNode> selected) {
285 if (selected != null) {
286 boolean hasXml = false;
287 for (UiElementNode uiNode : selected) {
288 if (uiNode.getXmlNode() != null) {
295 manager.add(new CopyCutAction(mEditor.getLayoutEditor(), mEditor.getClipboard(),
296 null, selected, true /* cut */));
297 manager.add(new CopyCutAction(mEditor.getLayoutEditor(), mEditor.getClipboard(),
298 null, selected, false /* cut */));
300 // Can't paste with more than one element selected (the selection is the target)
301 if (selected.size() <= 1) {
302 // Paste is not valid if it would add a second element on a terminal element
303 // which parent is a document -- an XML document can only have one child. This
304 // means paste is valid if the current UI node can have children or if the parent
305 // is not a document.
306 UiElementNode ui_root = selected.get(0).getUiRoot();
307 if (ui_root.getDescriptor().hasChildren() ||
308 !(ui_root.getUiParent() instanceof UiDocumentNode)) {
309 manager.add(new PasteAction(mEditor.getLayoutEditor(),
310 mEditor.getClipboard(),
314 manager.add(new Separator());
318 // Append "add" and "remove" actions. They do the same thing as the add/remove
319 // buttons on the side.
321 // "Add" makes sense only if there's 0 or 1 item selected since the
322 // one selected item becomes the target.
323 if (selected == null || selected.size() <= 1) {
324 manager.add(mAddAction);
327 if (selected != null) {
328 manager.add(mDeleteAction);
329 manager.add(new Separator());
331 manager.add(mUpAction);
332 manager.add(mDownAction);
335 if (selected != null && selected.size() == 1) {
336 manager.add(new Separator());
338 Action propertiesAction = new Action("Properties") {
341 EclipseUiHelper.showView(EclipseUiHelper.PROPERTY_SHEET_VIEW_ID,
342 true /* activate */);
345 propertiesAction.setToolTipText("Displays properties of the selected element.");
346 manager.add(propertiesAction);
351 * Updates the outline view with the model of the {@link IGraphicalLayoutEditor}.
353 * This attemps to preserve the selection, if any.
355 public void reloadModel() {
356 // Attemps to preserve the UiNode selection, if any
357 List<UiElementNode> uiNodes = null;
359 // get current selection using the model rather than the edit part as
360 // reloading the content may change the actual edit part.
361 uiNodes = getModelSelections();
363 // perform the update
364 getViewer().setContents(mEditor.getModel());
368 if (uiNodes != null) {
369 setModelSelection(uiNodes.get(0));
375 * Returns the currently selected element, if any, in the viewer.
376 * This returns the viewer's elements (i.e. an {@link UiElementTreeEditPart})
377 * and not the underlying model node.
379 * When there is no actual selection, this might still return the root node,
380 * which is of type {@link UiDocumentTreeEditPart}.
382 @SuppressWarnings("unchecked")
383 private List<UiElementTreeEditPart> getViewerSelections() {
384 ISelection selection = getSelection();
385 if (selection instanceof StructuredSelection) {
386 StructuredSelection structuredSelection = (StructuredSelection)selection;
388 if (structuredSelection.size() > 0) {
389 ArrayList<UiElementTreeEditPart> selected = new ArrayList<UiElementTreeEditPart>();
391 for (Iterator it = structuredSelection.iterator(); it.hasNext(); ) {
392 Object selectedObj = it.next();
394 if (selectedObj instanceof UiElementTreeEditPart) {
395 selected.add((UiElementTreeEditPart) selectedObj);
399 return selected.size() > 0 ? selected : null;
407 * Returns the currently selected model element, which is either an
408 * {@link UiViewTreeEditPart} or an {@link UiLayoutTreeEditPart}.
410 * Returns null if there is no selection or if the implicit root is "selected"
411 * (which actually represents the lack of a real element selection.)
413 private List<UiElementNode> getModelSelections() {
415 List<UiElementTreeEditPart> parts = getViewerSelections();
418 ArrayList<UiElementNode> selected = new ArrayList<UiElementNode>();
420 for (UiElementTreeEditPart part : parts) {
421 if (part instanceof UiViewTreeEditPart || part instanceof UiLayoutTreeEditPart) {
422 selected.add((UiElementNode) part.getModel());
426 return selected.size() > 0 ? selected : null;
433 * Selects the corresponding edit part in the tree viewer.
435 private void setViewerSelection(UiElementTreeEditPart selectedPart) {
436 if (selectedPart != null && !(selectedPart instanceof UiDocumentTreeEditPart)) {
437 LinkedList<UiElementTreeEditPart> segments = new LinkedList<UiElementTreeEditPart>();
438 for (UiElementTreeEditPart part = selectedPart;
439 !(part instanceof UiDocumentTreeEditPart);
440 part = (UiElementTreeEditPart) part.getParent()) {
441 segments.add(0, part);
443 setSelection(new TreeSelection(new TreePath(segments.toArray())));
448 * Selects the corresponding model element in the tree viewer.
450 private void setModelSelection(UiElementNode uiNodeToSelect) {
451 if (uiNodeToSelect != null) {
453 // find an edit part that has the requested model element
454 UiElementTreeEditPart part = findPartForModel(
455 (UiElementTreeEditPart) getViewer().getContents(),
458 // if we found a part, select it and reveal it
460 setViewerSelection(part);
461 getViewer().reveal(part);
467 * Utility method that tries to find an edit part that matches a given model UI node.
469 * @param rootPart The root of the viewer edit parts
470 * @param uiNode The UI node model to find
471 * @return The part that matches the model or null if it's not in the sub tree.
473 private UiElementTreeEditPart findPartForModel(UiElementTreeEditPart rootPart,
474 UiElementNode uiNode) {
475 if (rootPart.getModel() == uiNode) {
479 for (Object part : rootPart.getChildren()) {
480 if (part instanceof UiElementTreeEditPart) {
481 UiElementTreeEditPart found = findPartForModel(
482 (UiElementTreeEditPart) part, uiNode);
493 * Sets up a custom tooltip when hovering over tree items.
495 * The tooltip will display the element's javadoc, if any, or the item's getText otherwise.
497 private void setupTooltip() {
498 final Tree tree = (Tree) getControl();
502 * http://dev.eclipse.org/viewcvs/index.cgi/org.eclipse.swt.snippets/src/org/eclipse/swt/snippets/Snippet125.java?view=markup
505 final Listener listener = new Listener() {
509 public void handleEvent(Event event) {
529 String tooltip = null;
531 TreeItem item = tree.getItem(new Point(event.x, event.y));
533 Object data = item.getData();
534 if (data instanceof UiElementTreeEditPart) {
535 Object model = ((UiElementTreeEditPart) data).getModel();
536 if (model instanceof UiElementNode) {
537 tooltip = ((UiElementNode) model).getDescriptor().getTooltip();
541 if (tooltip == null) {
542 tooltip = item.getText();
544 tooltip = item.getText() + ":\r" + tooltip;
547 if (tooltip != null) {
548 Shell shell = tree.getShell();
549 Display display = tree.getDisplay();
551 tip = new Shell(shell, SWT.ON_TOP | SWT.NO_FOCUS | SWT.TOOL);
552 tip.setBackground(display .getSystemColor(SWT.COLOR_INFO_BACKGROUND));
553 FillLayout layout = new FillLayout();
554 layout.marginWidth = 2;
555 tip.setLayout(layout);
556 label = new Label(tip, SWT.NONE);
557 label.setForeground(display.getSystemColor(SWT.COLOR_INFO_FOREGROUND));
558 label.setBackground(display.getSystemColor(SWT.COLOR_INFO_BACKGROUND));
559 label.setData("_TABLEITEM", item);
560 label.setText(tooltip);
561 label.addListener(SWT.MouseExit, this);
562 label.addListener(SWT.MouseDown, this);
563 Point size = tip.computeSize(SWT.DEFAULT, SWT.DEFAULT);
564 Rectangle rect = item.getBounds(0);
565 Point pt = tree.toDisplay(rect.x, rect.y);
566 tip.setBounds(pt.x, pt.y, size.x, size.y);
567 tip.setVisible(true);
574 tree.addListener(SWT.Dispose, listener);
575 tree.addListener(SWT.KeyDown, listener);
576 tree.addListener(SWT.MouseMove, listener);
577 tree.addListener(SWT.MouseHover, listener);
581 * Sets up double-click action on the tree.
583 * By default, double-click (a.k.a. "default selection") on a valid list item will
584 * show the property view.
586 private void setupDoubleClick() {
587 final Tree tree = (Tree) getControl();
589 tree.addListener(SWT.DefaultSelection, new Listener() {
590 public void handleEvent(Event event) {
591 EclipseUiHelper.showView(EclipseUiHelper.PROPERTY_SHEET_VIEW_ID,
592 true /* activate */);
599 private class UiOutlineActions extends UiActions {
602 protected UiDocumentNode getRootNode() {
603 return mEditor.getModel(); // this is LayoutEditor.getUiRootNode()
606 // Select the new item
608 protected void selectUiNode(UiElementNode uiNodeToSelect) {
609 setModelSelection(uiNodeToSelect);
613 public void commitPendingXmlChanges() {
614 // Pass. There is nothing to commit before the XML is changed here.