2 * Copyright (C) 2010 The Android Open Source Project
4 * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
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.
17 package com.android.ide.eclipse.adt.internal.editors.layout.gle2;
19 import com.android.ide.eclipse.adt.AdtPlugin;
20 import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor;
21 import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditor;
22 import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode;
23 import com.android.ide.eclipse.adt.internal.editors.ui.ErrorImageComposite;
24 import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
26 import org.eclipse.jface.viewers.IElementComparer;
27 import org.eclipse.jface.viewers.ILabelProvider;
28 import org.eclipse.jface.viewers.ILabelProviderListener;
29 import org.eclipse.jface.viewers.ISelection;
30 import org.eclipse.jface.viewers.ISelectionChangedListener;
31 import org.eclipse.jface.viewers.ITreeContentProvider;
32 import org.eclipse.jface.viewers.TreePath;
33 import org.eclipse.jface.viewers.TreeSelection;
34 import org.eclipse.jface.viewers.TreeViewer;
35 import org.eclipse.jface.viewers.Viewer;
36 import org.eclipse.swt.SWT;
37 import org.eclipse.swt.graphics.Image;
38 import org.eclipse.swt.widgets.Composite;
39 import org.eclipse.swt.widgets.Control;
40 import org.eclipse.swt.widgets.Tree;
41 import org.eclipse.ui.IActionBars;
42 import org.eclipse.ui.views.contentoutline.IContentOutlinePage;
44 import java.util.ArrayList;
47 * TODO -- missing features:
48 * - synchronize selection tree=>canvas
49 * - right-click context menu *shared* with the one from canvas (simply delegate action)
50 * - drag'n'drop initiated from Palette to Outline
51 * - drag'n'drop from Outline to Outline
52 * - drag'n'drop from Canvas to Outline
53 * - drag'n'drop from Outline to Canvas
54 * - => Check if we can handle all the d'n'd cases a simply delegating the action to the canvas.
55 * There's a *lot* of logic in the CanvasDropListener we don't want to replicate.
56 * That should be fairly trivial, except that we can't provide X/Y coordinates in the drop
57 * move. We could just fake them by using the topleft/middle point of the tree item's bounds
58 * or something like that.
62 * An outline page for the GLE2 canvas view.
64 * The page is created by {@link LayoutEditor}.
65 * Selection is synchronized by {@link LayoutCanvas}.
67 public class OutlinePage2 implements IContentOutlinePage {
70 * The current TreeViewer. This is created in {@link #createControl(Composite)}.
71 * It is entirely possible for callbacks to be invoked *before* the tree viewer
72 * is created, for example if a non-yet shown canvas is modified and it refreshes
73 * the model of a non-yet shown outline.
75 private TreeViewer mTreeViewer;
78 * RootWrapper is a workaround: we can't set the input of the treeview to its root
79 * element, so we introduce a fake parent.
81 private final RootWrapper mRootWrapper = new RootWrapper();
83 public OutlinePage2() {
86 public void createControl(Composite parent) {
87 Tree tree = new Tree(parent, SWT.MULTI /*style*/);
88 mTreeViewer = new TreeViewer(tree);
90 mTreeViewer.setAutoExpandLevel(2);
91 mTreeViewer.setContentProvider(new ContentProvider());
92 mTreeViewer.setLabelProvider(new LabelProvider());
93 mTreeViewer.setInput(mRootWrapper);
95 // The tree viewer will hold CanvasViewInfo instances, however these
96 // change each time the canvas is reloaded. OTOH liblayout gives us
97 // constant UiView keys which we can use to perform tree item comparisons.
98 mTreeViewer.setComparer(new IElementComparer() {
99 public int hashCode(Object element) {
100 if (element instanceof CanvasViewInfo) {
101 UiViewElementNode key = ((CanvasViewInfo) element).getUiViewKey();
103 return key.hashCode();
106 if (element != null) {
107 return element.hashCode();
112 public boolean equals(Object a, Object b) {
113 if (a instanceof CanvasViewInfo && b instanceof CanvasViewInfo) {
114 UiViewElementNode keyA = ((CanvasViewInfo) a).getUiViewKey();
115 UiViewElementNode keyB = ((CanvasViewInfo) b).getUiViewKey();
117 return keyA.equals(keyB);
128 public void dispose() {
129 Control c = getControl();
130 if (c != null && !c.isDisposed()) {
134 mRootWrapper.setRoot(null);
137 public void setModel(CanvasViewInfo rootViewInfo) {
138 mRootWrapper.setRoot(rootViewInfo);
140 if (mTreeViewer != null) {
141 Object[] expanded = mTreeViewer.getExpandedElements();
142 mTreeViewer.refresh();
143 mTreeViewer.setExpandedElements(expanded);
147 public Control getControl() {
148 return mTreeViewer == null ? null : mTreeViewer.getControl();
151 public ISelection getSelection() {
152 return mTreeViewer == null ? null : mTreeViewer.getSelection();
156 * Selects the given {@link CanvasViewInfo} elements and reveals them.
158 * @param selectedInfos The {@link CanvasViewInfo} elements to selected.
159 * This can be null or empty to remove any selection.
161 public void selectAndReveal(CanvasViewInfo[] selectedInfos) {
162 if (mTreeViewer == null) {
166 if (selectedInfos == null || selectedInfos.length == 0) {
167 mTreeViewer.setSelection(TreeSelection.EMPTY);
171 int n = selectedInfos.length;
172 TreePath[] paths = new TreePath[n];
173 for (int i = 0; i < n; i++) {
174 ArrayList<Object> segments = new ArrayList<Object>();
175 CanvasViewInfo vi = selectedInfos[i];
180 paths[i] = new TreePath(segments.toArray());
181 mTreeViewer.expandToLevel(paths[i], 1);
184 mTreeViewer.setSelection(new TreeSelection(paths), true /*reveal*/);
187 public void setSelection(ISelection selection) {
188 if (mTreeViewer != null) {
189 mTreeViewer.setSelection(selection);
193 public void setFocus() {
194 Control c = getControl();
200 public void addSelectionChangedListener(ISelectionChangedListener listener) {
201 if (mTreeViewer != null) {
202 mTreeViewer.addSelectionChangedListener(listener);
206 public void removeSelectionChangedListener(ISelectionChangedListener listener) {
207 if (mTreeViewer != null) {
208 mTreeViewer.removeSelectionChangedListener(listener);
212 public void setActionBars(IActionBars barts) {
213 // TODO Auto-generated method stub
220 * In theory, the root of the model should be the input of the {@link TreeViewer},
221 * which would be the root {@link CanvasViewInfo}.
222 * That means in theory {@link ContentProvider#getElements(Object)} should return
223 * its own input as the single root node.
225 * However as described in JFace Bug 9262, this case is not properly handled by
226 * a {@link TreeViewer} and leads to an infinite recursion in the tree viewer.
227 * See https://bugs.eclipse.org/bugs/show_bug.cgi?id=9262
229 * The solution is to wrap the tree viewer input in a dummy root node that acts
230 * as a parent. This class does just that.
232 private static class RootWrapper {
233 private CanvasViewInfo mRoot;
235 public void setRoot(CanvasViewInfo root) {
239 public CanvasViewInfo getRoot() {
245 * Content provider for the Outline model.
246 * Objects are going to be {@link CanvasViewInfo}.
248 private static class ContentProvider implements ITreeContentProvider {
250 public Object[] getChildren(Object element) {
251 if (element instanceof RootWrapper) {
252 CanvasViewInfo root = ((RootWrapper)element).getRoot();
254 return new Object[] { root };
257 if (element instanceof CanvasViewInfo) {
258 ArrayList<CanvasViewInfo> children = ((CanvasViewInfo) element).getChildren();
259 if (children != null) {
260 return children.toArray();
263 return new Object[0];
266 public Object getParent(Object element) {
267 if (element instanceof CanvasViewInfo) {
268 return ((CanvasViewInfo) element).getParent();
273 public boolean hasChildren(Object element) {
274 if (element instanceof CanvasViewInfo) {
275 ArrayList<CanvasViewInfo> children = ((CanvasViewInfo) element).getChildren();
276 if (children != null) {
277 return children.size() > 0;
284 * Returns the root element.
285 * Semantically, the root element is the single top-level XML element of the XML layout.
287 public Object[] getElements(Object inputElement) {
288 return getChildren(inputElement);
291 public void dispose() {
295 public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
301 * Label provider for the Outline model.
302 * Objects are going to be {@link CanvasViewInfo}.
304 private static class LabelProvider implements ILabelProvider {
307 * Returns the element's logo with a fallback on the android logo.
309 public Image getImage(Object element) {
310 if (element instanceof CanvasViewInfo) {
311 element = ((CanvasViewInfo) element).getUiViewKey();
314 if (element instanceof UiElementNode) {
315 UiElementNode node = (UiElementNode) element;
316 ElementDescriptor desc = node.getDescriptor();
318 Image img = desc.getIcon();
320 if (node.hasError()) {
321 return new ErrorImageComposite(img).createImage();
329 return AdtPlugin.getAndroidLogo();
333 * Uses UiElementNode.shortDescription for the label for this tree item.
335 public String getText(Object element) {
336 if (element instanceof CanvasViewInfo) {
337 element = ((CanvasViewInfo) element).getUiViewKey();
340 if (element instanceof UiElementNode) {
341 UiElementNode node = (UiElementNode) element;
342 return node.getShortDescription();
345 return element == null ? "(null)" : element.toString(); //$NON-NLS-1$
348 public void addListener(ILabelProviderListener listener) {
352 public void dispose() {
356 public boolean isLabelProperty(Object element, String property) {
361 public void removeListener(ILabelProviderListener listener) {