OSDN Git Service

033732d769000ef43778fc91a002c052be2741fb
[android-x86/sdk.git] / eclipse / plugins / com.android.ide.eclipse.adt / src / com / android / ide / eclipse / adt / internal / editors / layout / gle2 / OutlinePage2.java
1 /*
2  * Copyright (C) 2010 The Android Open Source Project
3  *
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
7  *
8  *      http://www.eclipse.org/org/documents/epl-v10.php
9  *
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.
15  */
16
17 package com.android.ide.eclipse.adt.internal.editors.layout.gle2;
18
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;
25
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;
43
44 import java.util.ArrayList;
45
46 /*
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.
59  */
60
61 /**
62  * An outline page for the GLE2 canvas view.
63  * <p/>
64  * The page is created by {@link LayoutEditor}.
65  * Selection is synchronized by {@link LayoutCanvas}.
66  */
67 public class OutlinePage2 implements IContentOutlinePage {
68
69     /**
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.
74      */
75     private TreeViewer mTreeViewer;
76
77     /**
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.
80      */
81     private final RootWrapper mRootWrapper = new RootWrapper();
82
83     public OutlinePage2() {
84     }
85
86     public void createControl(Composite parent) {
87         Tree tree = new Tree(parent, SWT.MULTI /*style*/);
88         mTreeViewer = new TreeViewer(tree);
89
90         mTreeViewer.setAutoExpandLevel(2);
91         mTreeViewer.setContentProvider(new ContentProvider());
92         mTreeViewer.setLabelProvider(new LabelProvider());
93         mTreeViewer.setInput(mRootWrapper);
94
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();
102                     if (key != null) {
103                         return key.hashCode();
104                     }
105                 }
106                 if (element != null) {
107                     return element.hashCode();
108                 }
109                 return 0;
110             }
111
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();
116                     if (keyA != null) {
117                         return keyA.equals(keyB);
118                     }
119                 }
120                 if (a != null) {
121                     return a.equals(b);
122                 }
123                 return false;
124             }
125         });
126     }
127
128     public void dispose() {
129         Control c = getControl();
130         if (c != null && !c.isDisposed()) {
131             mTreeViewer = null;
132             c.dispose();
133         }
134         mRootWrapper.setRoot(null);
135     }
136
137     public void setModel(CanvasViewInfo rootViewInfo) {
138         mRootWrapper.setRoot(rootViewInfo);
139
140         if (mTreeViewer != null) {
141             Object[] expanded = mTreeViewer.getExpandedElements();
142             mTreeViewer.refresh();
143             mTreeViewer.setExpandedElements(expanded);
144         }
145     }
146
147     public Control getControl() {
148         return mTreeViewer == null ? null : mTreeViewer.getControl();
149     }
150
151     public ISelection getSelection() {
152         return mTreeViewer == null ? null : mTreeViewer.getSelection();
153     }
154
155     /**
156      * Selects the given {@link CanvasViewInfo} elements and reveals them.
157      *
158      * @param selectedInfos The {@link CanvasViewInfo} elements to selected.
159      *   This can be null or empty to remove any selection.
160      */
161     public void selectAndReveal(CanvasViewInfo[] selectedInfos) {
162         if (mTreeViewer == null) {
163             return;
164         }
165
166         if (selectedInfos == null || selectedInfos.length == 0) {
167             mTreeViewer.setSelection(TreeSelection.EMPTY);
168             return;
169         }
170
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];
176             while (vi != null) {
177                 segments.add(0, vi);
178                 vi = vi.getParent();
179             }
180             paths[i] = new TreePath(segments.toArray());
181             mTreeViewer.expandToLevel(paths[i], 1);
182         }
183
184         mTreeViewer.setSelection(new TreeSelection(paths), true /*reveal*/);
185     }
186
187     public void setSelection(ISelection selection) {
188         if (mTreeViewer != null) {
189             mTreeViewer.setSelection(selection);
190         }
191     }
192
193     public void setFocus() {
194         Control c = getControl();
195         if (c != null) {
196             c.setFocus();
197         }
198     }
199
200     public void addSelectionChangedListener(ISelectionChangedListener listener) {
201         if (mTreeViewer != null) {
202             mTreeViewer.addSelectionChangedListener(listener);
203         }
204     }
205
206     public void removeSelectionChangedListener(ISelectionChangedListener listener) {
207         if (mTreeViewer != null) {
208             mTreeViewer.removeSelectionChangedListener(listener);
209         }
210     }
211
212     public void setActionBars(IActionBars barts) {
213         // TODO Auto-generated method stub
214     }
215
216     // ----
217
218
219     /**
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.
224      * <p/>
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
228      * <p/>
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.
231      */
232     private static class RootWrapper {
233         private CanvasViewInfo mRoot;
234
235         public void setRoot(CanvasViewInfo root) {
236             mRoot = root;
237         }
238
239         public CanvasViewInfo getRoot() {
240             return mRoot;
241         }
242     }
243
244     /**
245      * Content provider for the Outline model.
246      * Objects are going to be {@link CanvasViewInfo}.
247      */
248     private static class ContentProvider implements ITreeContentProvider {
249
250         public Object[] getChildren(Object element) {
251             if (element instanceof RootWrapper) {
252                 CanvasViewInfo root = ((RootWrapper)element).getRoot();
253                 if (root != null) {
254                     return new Object[] { root };
255                 }
256             }
257             if (element instanceof CanvasViewInfo) {
258                 ArrayList<CanvasViewInfo> children = ((CanvasViewInfo) element).getChildren();
259                 if (children != null) {
260                     return children.toArray();
261                 }
262             }
263             return new Object[0];
264         }
265
266         public Object getParent(Object element) {
267             if (element instanceof CanvasViewInfo) {
268                 return ((CanvasViewInfo) element).getParent();
269             }
270             return null;
271         }
272
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;
278                 }
279             }
280             return false;
281         }
282
283         /**
284          * Returns the root element.
285          * Semantically, the root element is the single top-level XML element of the XML layout.
286          */
287         public Object[] getElements(Object inputElement) {
288             return getChildren(inputElement);
289         }
290
291         public void dispose() {
292             // pass
293         }
294
295         public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
296             // pass
297         }
298     }
299
300     /**
301      * Label provider for the Outline model.
302      * Objects are going to be {@link CanvasViewInfo}.
303      */
304     private static class LabelProvider implements ILabelProvider {
305
306         /**
307          * Returns the element's logo with a fallback on the android logo.
308          */
309         public Image getImage(Object element) {
310             if (element instanceof CanvasViewInfo) {
311                 element = ((CanvasViewInfo) element).getUiViewKey();
312             }
313
314             if (element instanceof UiElementNode) {
315                 UiElementNode node = (UiElementNode) element;
316                 ElementDescriptor desc = node.getDescriptor();
317                 if (desc != null) {
318                     Image img = desc.getIcon();
319                     if (img != null) {
320                         if (node.hasError()) {
321                             return new ErrorImageComposite(img).createImage();
322                         } else {
323                             return img;
324                         }
325                     }
326                 }
327             }
328
329             return AdtPlugin.getAndroidLogo();
330         }
331
332         /**
333          * Uses UiElementNode.shortDescription for the label for this tree item.
334          */
335         public String getText(Object element) {
336             if (element instanceof CanvasViewInfo) {
337                 element = ((CanvasViewInfo) element).getUiViewKey();
338             }
339
340             if (element instanceof UiElementNode) {
341                 UiElementNode node = (UiElementNode) element;
342                 return node.getShortDescription();
343             }
344
345             return element == null ? "(null)" : element.toString();  //$NON-NLS-1$
346         }
347
348         public void addListener(ILabelProviderListener listener) {
349             // pass
350         }
351
352         public void dispose() {
353             // pass
354         }
355
356         public boolean isLabelProperty(Object element, String property) {
357             // pass
358             return false;
359         }
360
361         public void removeListener(ILabelProviderListener listener) {
362             // pass
363         }
364     }
365
366 }