• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.ITreeContentProvider;
31 import org.eclipse.jface.viewers.ITreeSelection;
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.graphics.Image;
37 import org.eclipse.swt.widgets.Composite;
38 import org.eclipse.ui.INullSelectionListener;
39 import org.eclipse.ui.ISelectionListener;
40 import org.eclipse.ui.IWorkbenchPart;
41 import org.eclipse.ui.views.contentoutline.ContentOutlinePage;
42 
43 import java.util.ArrayList;
44 
45 /*
46  * TODO -- missing features:
47  * - right-click context menu *shared* with the one from canvas (simply delegate action)
48  * - drag'n'drop initiated from Palette to Outline
49  * - drag'n'drop from Outline to Outline
50  * - drag'n'drop from Canvas to Outline
51  * - drag'n'drop from Outline to Canvas
52  * - => Check if we can handle all the d'n'd cases a simply delegating the action to the canvas.
53  *      There's a *lot* of logic in the CanvasDropListener we don't want to replicate.
54  *      That should be fairly trivial, except that we can't provide X/Y coordinates in the drop
55  *      move. We could just fake them by using the topleft/middle point of the tree item's bounds
56  *      or something like that.
57  */
58 
59 /**
60  * An outline page for the GLE2 canvas view.
61  * <p/>
62  * The page is created by {@link LayoutEditor#getAdapter(Class)}.
63  * It sets itself as a listener on the site's selection service in order to be
64  * notified of the canvas' selection changes.
65  * The underlying page is also a selection provider (via IContentOutlinePage)
66  * and as such it will broadcast selection changes to the site's selection service
67  * (on which both the layout editor part and the property sheet page listen.)
68  *
69  * @since GLE2
70  */
71 public class OutlinePage2 extends ContentOutlinePage
72     implements ISelectionListener, INullSelectionListener {
73 
74     /**
75      * RootWrapper is a workaround: we can't set the input of the treeview to its root
76      * element, so we introduce a fake parent.
77      */
78     private final RootWrapper mRootWrapper = new RootWrapper();
79 
OutlinePage2()80     public OutlinePage2() {
81         super();
82     }
83 
84     @Override
createControl(Composite parent)85     public void createControl(Composite parent) {
86         super.createControl(parent);
87 
88         TreeViewer tv = getTreeViewer();
89         tv.setAutoExpandLevel(2);
90         tv.setContentProvider(new ContentProvider());
91         tv.setLabelProvider(new LabelProvider());
92         tv.setInput(mRootWrapper);
93 
94         // The tree viewer will hold CanvasViewInfo instances, however these
95         // change each time the canvas is reloaded. OTOH liblayout gives us
96         // constant UiView keys which we can use to perform tree item comparisons.
97         tv.setComparer(new IElementComparer() {
98             public int hashCode(Object element) {
99                 if (element instanceof CanvasViewInfo) {
100                     UiViewElementNode key = ((CanvasViewInfo) element).getUiViewKey();
101                     if (key != null) {
102                         return key.hashCode();
103                     }
104                 }
105                 if (element != null) {
106                     return element.hashCode();
107                 }
108                 return 0;
109             }
110 
111             public boolean equals(Object a, Object b) {
112                 if (a instanceof CanvasViewInfo && b instanceof CanvasViewInfo) {
113                     UiViewElementNode keyA = ((CanvasViewInfo) a).getUiViewKey();
114                     UiViewElementNode keyB = ((CanvasViewInfo) b).getUiViewKey();
115                     if (keyA != null) {
116                         return keyA.equals(keyB);
117                     }
118                 }
119                 if (a != null) {
120                     return a.equals(b);
121                 }
122                 return false;
123             }
124         });
125 
126         // Listen to selection changes from the layout editor
127         getSite().getPage().addSelectionListener(this);
128     }
129 
130     @Override
dispose()131     public void dispose() {
132         mRootWrapper.setRoot(null);
133         getSite().getPage().removeSelectionListener(this);
134         super.dispose();
135     }
136 
137     /**
138      * Invoked by {@link LayoutCanvas} to set the model (aka the root view info).
139      *
140      * @param rootViewInfo The root of the view info hierarchy. Can be null.
141      */
setModel(CanvasViewInfo rootViewInfo)142     public void setModel(CanvasViewInfo rootViewInfo) {
143         mRootWrapper.setRoot(rootViewInfo);
144 
145         TreeViewer tv = getTreeViewer();
146         if (tv != null) {
147             Object[] expanded = tv.getExpandedElements();
148             tv.refresh();
149             tv.setExpandedElements(expanded);
150         }
151     }
152 
153     /**
154      * Returns the current tree viewer selection. Shouldn't be null,
155      * although it can be {@link TreeSelection#EMPTY}.
156      */
157     @Override
getSelection()158     public ISelection getSelection() {
159         return super.getSelection();
160     }
161 
162     /**
163      * Sets the outline selection.
164      *
165      * @param selection Only {@link ITreeSelection} will be used, otherwise the
166      *   selection will be cleared (including a null selection).
167      */
168     @Override
setSelection(ISelection selection)169     public void setSelection(ISelection selection) {
170         // TreeViewer should be able to deal with a null selection, but let's make it safe
171         if (selection == null) {
172             selection = TreeSelection.EMPTY;
173         }
174 
175         super.setSelection(selection);
176 
177         TreeViewer tv = getTreeViewer();
178         if (tv == null || !(selection instanceof ITreeSelection) || selection.isEmpty()) {
179             return;
180         }
181 
182         // auto-reveal the selection
183         ITreeSelection treeSel = (ITreeSelection) selection;
184         for (TreePath p : treeSel.getPaths()) {
185             tv.expandToLevel(p, 1);
186         }
187     }
188 
189     /**
190      * Listens to a workbench selection.
191      * Only listen on selection coming from {@link LayoutEditor}, which avoid
192      * picking up our own selections.
193      */
selectionChanged(IWorkbenchPart part, ISelection selection)194     public void selectionChanged(IWorkbenchPart part, ISelection selection) {
195         if (part instanceof LayoutEditor) {
196             setSelection(selection);
197         }
198     }
199 
200     // ----
201 
202 
203     /**
204      * In theory, the root of the model should be the input of the {@link TreeViewer},
205      * which would be the root {@link CanvasViewInfo}.
206      * That means in theory {@link ContentProvider#getElements(Object)} should return
207      * its own input as the single root node.
208      * <p/>
209      * However as described in JFace Bug 9262, this case is not properly handled by
210      * a {@link TreeViewer} and leads to an infinite recursion in the tree viewer.
211      * See https://bugs.eclipse.org/bugs/show_bug.cgi?id=9262
212      * <p/>
213      * The solution is to wrap the tree viewer input in a dummy root node that acts
214      * as a parent. This class does just that.
215      */
216     private static class RootWrapper {
217         private CanvasViewInfo mRoot;
218 
setRoot(CanvasViewInfo root)219         public void setRoot(CanvasViewInfo root) {
220             mRoot = root;
221         }
222 
getRoot()223         public CanvasViewInfo getRoot() {
224             return mRoot;
225         }
226     }
227 
228     /**
229      * Content provider for the Outline model.
230      * Objects are going to be {@link CanvasViewInfo}.
231      */
232     private static class ContentProvider implements ITreeContentProvider {
233 
getChildren(Object element)234         public Object[] getChildren(Object element) {
235             if (element instanceof RootWrapper) {
236                 CanvasViewInfo root = ((RootWrapper)element).getRoot();
237                 if (root != null) {
238                     return new Object[] { root };
239                 }
240             }
241             if (element instanceof CanvasViewInfo) {
242                 ArrayList<CanvasViewInfo> children = ((CanvasViewInfo) element).getChildren();
243                 if (children != null) {
244                     return children.toArray();
245                 }
246             }
247             return new Object[0];
248         }
249 
getParent(Object element)250         public Object getParent(Object element) {
251             if (element instanceof CanvasViewInfo) {
252                 return ((CanvasViewInfo) element).getParent();
253             }
254             return null;
255         }
256 
hasChildren(Object element)257         public boolean hasChildren(Object element) {
258             if (element instanceof CanvasViewInfo) {
259                 ArrayList<CanvasViewInfo> children = ((CanvasViewInfo) element).getChildren();
260                 if (children != null) {
261                     return children.size() > 0;
262                 }
263             }
264             return false;
265         }
266 
267         /**
268          * Returns the root element.
269          * Semantically, the root element is the single top-level XML element of the XML layout.
270          */
getElements(Object inputElement)271         public Object[] getElements(Object inputElement) {
272             return getChildren(inputElement);
273         }
274 
dispose()275         public void dispose() {
276             // pass
277         }
278 
inputChanged(Viewer viewer, Object oldInput, Object newInput)279         public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
280             // pass
281         }
282     }
283 
284     /**
285      * Label provider for the Outline model.
286      * Objects are going to be {@link CanvasViewInfo}.
287      */
288     private static class LabelProvider implements ILabelProvider {
289 
290         /**
291          * Returns the element's logo with a fallback on the android logo.
292          */
getImage(Object element)293         public Image getImage(Object element) {
294             if (element instanceof CanvasViewInfo) {
295                 element = ((CanvasViewInfo) element).getUiViewKey();
296             }
297 
298             if (element instanceof UiElementNode) {
299                 UiElementNode node = (UiElementNode) element;
300                 ElementDescriptor desc = node.getDescriptor();
301                 if (desc != null) {
302                     Image img = desc.getIcon();
303                     if (img != null) {
304                         if (node.hasError()) {
305                             return new ErrorImageComposite(img).createImage();
306                         } else {
307                             return img;
308                         }
309                     }
310                 }
311             }
312 
313             return AdtPlugin.getAndroidLogo();
314         }
315 
316         /**
317          * Uses UiElementNode.shortDescription for the label for this tree item.
318          */
getText(Object element)319         public String getText(Object element) {
320             if (element instanceof CanvasViewInfo) {
321                 element = ((CanvasViewInfo) element).getUiViewKey();
322             }
323 
324             if (element instanceof UiElementNode) {
325                 UiElementNode node = (UiElementNode) element;
326                 return node.getShortDescription();
327             }
328 
329             return element == null ? "(null)" : element.toString();  //$NON-NLS-1$
330         }
331 
addListener(ILabelProviderListener listener)332         public void addListener(ILabelProviderListener listener) {
333             // pass
334         }
335 
dispose()336         public void dispose() {
337             // pass
338         }
339 
isLabelProperty(Object element, String property)340         public boolean isLabelProperty(Object element, String property) {
341             // pass
342             return false;
343         }
344 
removeListener(ILabelProviderListener listener)345         public void removeListener(ILabelProviderListener listener) {
346             // pass
347         }
348     }
349 }
350