• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2008 The Android Open Source Project
3  *
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
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
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 
18 package com.android.ide.eclipse.adt.internal.editors.layout;
19 
20 import com.android.ide.eclipse.adt.internal.editors.IconFactory;
21 import com.android.ide.eclipse.adt.internal.editors.layout.parts.UiDocumentTreeEditPart;
22 import com.android.ide.eclipse.adt.internal.editors.layout.parts.UiElementTreeEditPart;
23 import com.android.ide.eclipse.adt.internal.editors.layout.parts.UiElementTreeEditPartFactory;
24 import com.android.ide.eclipse.adt.internal.editors.layout.parts.UiLayoutTreeEditPart;
25 import com.android.ide.eclipse.adt.internal.editors.layout.parts.UiViewTreeEditPart;
26 import com.android.ide.eclipse.adt.internal.editors.ui.tree.CopyCutAction;
27 import com.android.ide.eclipse.adt.internal.editors.ui.tree.PasteAction;
28 import com.android.ide.eclipse.adt.internal.editors.ui.tree.UiActions;
29 import com.android.ide.eclipse.adt.internal.editors.uimodel.UiDocumentNode;
30 import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
31 import com.android.ide.eclipse.adt.internal.ui.EclipseUiHelper;
32 
33 import org.eclipse.gef.EditPartViewer;
34 import org.eclipse.gef.ui.parts.ContentOutlinePage;
35 import org.eclipse.jface.action.Action;
36 import org.eclipse.jface.action.IMenuListener;
37 import org.eclipse.jface.action.IMenuManager;
38 import org.eclipse.jface.action.IToolBarManager;
39 import org.eclipse.jface.action.MenuManager;
40 import org.eclipse.jface.action.Separator;
41 import org.eclipse.jface.viewers.ISelection;
42 import org.eclipse.jface.viewers.ISelectionChangedListener;
43 import org.eclipse.jface.viewers.SelectionChangedEvent;
44 import org.eclipse.jface.viewers.StructuredSelection;
45 import org.eclipse.jface.viewers.TreePath;
46 import org.eclipse.jface.viewers.TreeSelection;
47 import org.eclipse.swt.SWT;
48 import org.eclipse.swt.graphics.Point;
49 import org.eclipse.swt.graphics.Rectangle;
50 import org.eclipse.swt.layout.FillLayout;
51 import org.eclipse.swt.widgets.Composite;
52 import org.eclipse.swt.widgets.Control;
53 import org.eclipse.swt.widgets.Display;
54 import org.eclipse.swt.widgets.Event;
55 import org.eclipse.swt.widgets.Label;
56 import org.eclipse.swt.widgets.Listener;
57 import org.eclipse.swt.widgets.Menu;
58 import org.eclipse.swt.widgets.Shell;
59 import org.eclipse.swt.widgets.Tree;
60 import org.eclipse.swt.widgets.TreeItem;
61 import org.eclipse.ui.IActionBars;
62 
63 import java.util.ArrayList;
64 import java.util.Iterator;
65 import java.util.LinkedList;
66 import java.util.List;
67 
68 /**
69  * Implementation of the {@link ContentOutlinePage} to display {@link UiElementNode}.
70  *
71  * @since GLE1
72  */
73 class UiContentOutlinePage extends ContentOutlinePage {
74 
75     private GraphicalLayoutEditor mEditor;
76 
77     private Action mAddAction;
78     private Action mDeleteAction;
79     private Action mUpAction;
80     private Action mDownAction;
81 
82     private UiOutlineActions mUiActions = new UiOutlineActions();
83 
UiContentOutlinePage(GraphicalLayoutEditor editor, final EditPartViewer viewer)84     public UiContentOutlinePage(GraphicalLayoutEditor editor, final EditPartViewer viewer) {
85         super(viewer);
86         mEditor = editor;
87         IconFactory factory = IconFactory.getInstance();
88 
89         mAddAction = new Action("Add...") {
90             @Override
91             public void run() {
92                 List<UiElementNode> nodes = getModelSelections();
93                 UiElementNode node = nodes != null && nodes.size() > 0 ? nodes.get(0) : null;
94 
95                 mUiActions.doAdd(node, viewer.getControl().getShell());
96             }
97         };
98         mAddAction.setToolTipText("Adds a new element.");
99         mAddAction.setImageDescriptor(factory.getImageDescriptor("add")); //$NON-NLS-1$
100 
101         mDeleteAction = new Action("Remove...") {
102             @Override
103             public void run() {
104                 List<UiElementNode> nodes = getModelSelections();
105 
106                 mUiActions.doRemove(nodes, viewer.getControl().getShell());
107             }
108         };
109         mDeleteAction.setToolTipText("Removes an existing selected element.");
110         mDeleteAction.setImageDescriptor(factory.getImageDescriptor("delete")); //$NON-NLS-1$
111 
112         mUpAction = new Action("Up") {
113             @Override
114             public void run() {
115                 List<UiElementNode> nodes = getModelSelections();
116 
117                 mUiActions.doUp(nodes);
118             }
119         };
120         mUpAction.setToolTipText("Moves the selected element up");
121         mUpAction.setImageDescriptor(factory.getImageDescriptor("up")); //$NON-NLS-1$
122 
123         mDownAction = new Action("Down") {
124             @Override
125             public void run() {
126                 List<UiElementNode> nodes = getModelSelections();
127 
128                 mUiActions.doDown(nodes);
129             }
130         };
131         mDownAction.setToolTipText("Moves the selected element down");
132         mDownAction.setImageDescriptor(factory.getImageDescriptor("down")); //$NON-NLS-1$
133 
134         // all actions disabled by default.
135         mAddAction.setEnabled(false);
136         mDeleteAction.setEnabled(false);
137         mUpAction.setEnabled(false);
138         mDownAction.setEnabled(false);
139 
140         addSelectionChangedListener(new ISelectionChangedListener() {
141             public void selectionChanged(SelectionChangedEvent event) {
142                 ISelection selection = event.getSelection();
143 
144                 // the selection is never empty. The least it'll contain is the
145                 // UiDocumentTreeEditPart object.
146                 if (selection instanceof StructuredSelection) {
147                     StructuredSelection structSel = (StructuredSelection)selection;
148 
149                     if (structSel.size() == 1 &&
150                             structSel.getFirstElement() instanceof UiDocumentTreeEditPart) {
151                         mDeleteAction.setEnabled(false);
152                         mUpAction.setEnabled(false);
153                         mDownAction.setEnabled(false);
154                     } else {
155                         mDeleteAction.setEnabled(true);
156                         mUpAction.setEnabled(true);
157                         mDownAction.setEnabled(true);
158                     }
159 
160                     // the "add" button is always enabled, in order to be able to set the
161                     // initial root node
162                     mAddAction.setEnabled(true);
163                 }
164             }
165         });
166     }
167 
168 
169     /* (non-Javadoc)
170      * @see org.eclipse.ui.part.IPage#createControl(org.eclipse.swt.widgets.Composite)
171      */
172     @Override
createControl(Composite parent)173     public void createControl(Composite parent) {
174         // create outline viewer page
175         getViewer().createControl(parent);
176 
177         // configure outline viewer
178         getViewer().setEditPartFactory(new UiElementTreeEditPartFactory());
179 
180         setupOutline();
181         setupContextMenu();
182         setupTooltip();
183         setupDoubleClick();
184     }
185 
186     /*
187      * (non-Javadoc)
188      * @see org.eclipse.ui.part.Page#setActionBars(org.eclipse.ui.IActionBars)
189      *
190      * Called automatically after createControl
191      */
192     @Override
setActionBars(IActionBars actionBars)193     public void setActionBars(IActionBars actionBars) {
194         IToolBarManager toolBarManager = actionBars.getToolBarManager();
195         toolBarManager.add(mAddAction);
196         toolBarManager.add(mDeleteAction);
197         toolBarManager.add(new Separator());
198         toolBarManager.add(mUpAction);
199         toolBarManager.add(mDownAction);
200 
201         IMenuManager menuManager = actionBars.getMenuManager();
202         menuManager.add(mAddAction);
203         menuManager.add(mDeleteAction);
204         menuManager.add(new Separator());
205         menuManager.add(mUpAction);
206         menuManager.add(mDownAction);
207     }
208 
209     /* (non-Javadoc)
210      * @see org.eclipse.ui.part.IPage#dispose()
211      */
212     @Override
dispose()213     public void dispose() {
214         breakConnectionWithEditor();
215 
216         // dispose
217         super.dispose();
218     }
219 
220     /* (non-Javadoc)
221      * @see org.eclipse.ui.part.IPage#getControl()
222      */
223     @Override
getControl()224     public Control getControl() {
225         return getViewer().getControl();
226     }
227 
setNewEditor(GraphicalLayoutEditor editor)228     void setNewEditor(GraphicalLayoutEditor editor) {
229         mEditor = editor;
230         setupOutline();
231     }
232 
breakConnectionWithEditor()233     void breakConnectionWithEditor() {
234         // unhook outline viewer
235         mEditor.getSelectionSynchronizer().removeViewer(getViewer());
236     }
237 
setupOutline()238     private void setupOutline() {
239 
240         getViewer().setEditDomain(mEditor.getEditDomain());
241 
242         // hook outline viewer
243         mEditor.getSelectionSynchronizer().addViewer(getViewer());
244 
245         // initialize outline viewer with model
246         getViewer().setContents(mEditor.getModel());
247     }
248 
setupContextMenu()249     private void setupContextMenu() {
250         MenuManager menuManager = new MenuManager();
251         menuManager.setRemoveAllWhenShown(true);
252         menuManager.addMenuListener(new IMenuListener() {
253             /**
254              * The menu is about to be shown. The menu manager has already been
255              * requested to remove any existing menu item. This method gets the
256              * tree selection and if it is of the appropriate type it re-creates
257              * the necessary actions.
258              */
259            public void menuAboutToShow(IMenuManager manager) {
260                List<UiElementNode> selected = getModelSelections();
261 
262                if (selected != null) {
263                    doCreateMenuAction(manager, selected);
264                    return;
265                }
266                doCreateMenuAction(manager, null /* ui_node */);
267             }
268         });
269         Control control = getControl();
270         Menu contextMenu = menuManager.createContextMenu(control);
271         control.setMenu(contextMenu);
272     }
273 
274     /**
275      * Adds the menu actions to the context menu when the given UI node is selected in
276      * the tree view.
277      *
278      * @param manager The context menu manager
279      * @param selected The UI node selected in the tree. Can be null, in which case the root
280      *                is to be modified.
281      */
doCreateMenuAction(IMenuManager manager, List<UiElementNode> selected)282     private void doCreateMenuAction(IMenuManager manager, List<UiElementNode> selected) {
283 
284         if (selected != null) {
285             boolean hasXml = false;
286             for (UiElementNode uiNode : selected) {
287                 if (uiNode.getXmlNode() != null) {
288                     hasXml = true;
289                     break;
290                 }
291             }
292 
293             if (hasXml) {
294                 manager.add(new CopyCutAction(mEditor.getLayoutEditor(), mEditor.getClipboard(),
295                         null, selected, true /* cut */));
296                 manager.add(new CopyCutAction(mEditor.getLayoutEditor(), mEditor.getClipboard(),
297                         null, selected, false /* cut */));
298 
299                 // Can't paste with more than one element selected (the selection is the target)
300                 if (selected.size() <= 1) {
301                     // Paste is not valid if it would add a second element on a terminal element
302                     // which parent is a document -- an XML document can only have one child. This
303                     // means paste is valid if the current UI node can have children or if the parent
304                     // is not a document.
305                     UiElementNode ui_root = selected.get(0).getUiRoot();
306                     if (ui_root.getDescriptor().hasChildren() ||
307                             !(ui_root.getUiParent() instanceof UiDocumentNode)) {
308                         manager.add(new PasteAction(mEditor.getLayoutEditor(),
309                                 mEditor.getClipboard(),
310                                 selected.get(0)));
311                     }
312                 }
313                 manager.add(new Separator());
314             }
315         }
316 
317         // Append "add" and "remove" actions. They do the same thing as the add/remove
318         // buttons on the side.
319         //
320         // "Add" makes sense only if there's 0 or 1 item selected since the
321         // one selected item becomes the target.
322         if (selected == null || selected.size() <= 1) {
323             manager.add(mAddAction);
324         }
325 
326         if (selected != null) {
327             manager.add(mDeleteAction);
328             manager.add(new Separator());
329 
330             manager.add(mUpAction);
331             manager.add(mDownAction);
332         }
333 
334         if (selected != null && selected.size() == 1) {
335             manager.add(new Separator());
336 
337             Action propertiesAction = new Action("Properties") {
338                 @Override
339                 public void run() {
340                     EclipseUiHelper.showView(EclipseUiHelper.PROPERTY_SHEET_VIEW_ID,
341                             true /* activate */);
342                 }
343             };
344             propertiesAction.setToolTipText("Displays properties of the selected element.");
345             manager.add(propertiesAction);
346         }
347     }
348 
349     /**
350      * Updates the outline view with the model of the {@link IGraphicalLayoutEditor}.
351      * <p/>
352      * This attemps to preserve the selection, if any.
353      */
reloadModel()354     public void reloadModel() {
355         // Attemps to preserve the UiNode selection, if any
356         List<UiElementNode> uiNodes = null;
357         try {
358             // get current selection using the model rather than the edit part as
359             // reloading the content may change the actual edit part.
360             uiNodes = getModelSelections();
361 
362             // perform the update
363             getViewer().setContents(mEditor.getModel());
364 
365         } finally {
366             // restore selection
367             if (uiNodes != null) {
368                 setModelSelection(uiNodes.get(0));
369             }
370         }
371     }
372 
373     /**
374      * Returns the currently selected element, if any, in the viewer.
375      * This returns the viewer's elements (i.e. an {@link UiElementTreeEditPart})
376      * and not the underlying model node.
377      * <p/>
378      * When there is no actual selection, this might still return the root node,
379      * which is of type {@link UiDocumentTreeEditPart}.
380      */
381     @SuppressWarnings("unchecked")
getViewerSelections()382     private List<UiElementTreeEditPart> getViewerSelections() {
383         ISelection selection = getSelection();
384         if (selection instanceof StructuredSelection) {
385             StructuredSelection structuredSelection = (StructuredSelection)selection;
386 
387             if (structuredSelection.size() > 0) {
388                 ArrayList<UiElementTreeEditPart> selected = new ArrayList<UiElementTreeEditPart>();
389 
390                 for (Iterator it = structuredSelection.iterator(); it.hasNext(); ) {
391                     Object selectedObj = it.next();
392 
393                     if (selectedObj instanceof UiElementTreeEditPart) {
394                         selected.add((UiElementTreeEditPart) selectedObj);
395                     }
396                 }
397 
398                 return selected.size() > 0 ? selected : null;
399             }
400         }
401 
402         return null;
403     }
404 
405     /**
406      * Returns the currently selected model element, which is either an
407      * {@link UiViewTreeEditPart} or an {@link UiLayoutTreeEditPart}.
408      * <p/>
409      * Returns null if there is no selection or if the implicit root is "selected"
410      * (which actually represents the lack of a real element selection.)
411      */
getModelSelections()412     private List<UiElementNode> getModelSelections() {
413 
414         List<UiElementTreeEditPart> parts = getViewerSelections();
415 
416         if (parts != null) {
417             ArrayList<UiElementNode> selected = new ArrayList<UiElementNode>();
418 
419             for (UiElementTreeEditPart part : parts) {
420                 if (part instanceof UiViewTreeEditPart || part instanceof UiLayoutTreeEditPart) {
421                     selected.add((UiElementNode) part.getModel());
422                 }
423             }
424 
425             return selected.size() > 0 ? selected : null;
426         }
427 
428         return null;
429     }
430 
431     /**
432      * Selects the corresponding edit part in the tree viewer.
433      */
setViewerSelection(UiElementTreeEditPart selectedPart)434     private void setViewerSelection(UiElementTreeEditPart selectedPart) {
435         if (selectedPart != null && !(selectedPart instanceof UiDocumentTreeEditPart)) {
436             LinkedList<UiElementTreeEditPart> segments = new LinkedList<UiElementTreeEditPart>();
437             for (UiElementTreeEditPart part = selectedPart;
438                     !(part instanceof UiDocumentTreeEditPart);
439                     part = (UiElementTreeEditPart) part.getParent()) {
440                 segments.add(0, part);
441             }
442             setSelection(new TreeSelection(new TreePath(segments.toArray())));
443         }
444     }
445 
446     /**
447      * Selects the corresponding model element in the tree viewer.
448      */
setModelSelection(UiElementNode uiNodeToSelect)449     private void setModelSelection(UiElementNode uiNodeToSelect) {
450         if (uiNodeToSelect != null) {
451 
452             // find an edit part that has the requested model element
453             UiElementTreeEditPart part = findPartForModel(
454                     (UiElementTreeEditPart) getViewer().getContents(),
455                     uiNodeToSelect);
456 
457             // if we found a part, select it and reveal it
458             if (part != null) {
459                 setViewerSelection(part);
460                 getViewer().reveal(part);
461             }
462         }
463     }
464 
465     /**
466      * Utility method that tries to find an edit part that matches a given model UI node.
467      *
468      * @param rootPart The root of the viewer edit parts
469      * @param uiNode The UI node model to find
470      * @return The part that matches the model or null if it's not in the sub tree.
471      */
findPartForModel(UiElementTreeEditPart rootPart, UiElementNode uiNode)472     private UiElementTreeEditPart findPartForModel(UiElementTreeEditPart rootPart,
473             UiElementNode uiNode) {
474         if (rootPart.getModel() == uiNode) {
475             return rootPart;
476         }
477 
478         for (Object part : rootPart.getChildren()) {
479             if (part instanceof UiElementTreeEditPart) {
480                 UiElementTreeEditPart found = findPartForModel(
481                         (UiElementTreeEditPart) part, uiNode);
482                 if (found != null) {
483                     return found;
484                 }
485             }
486         }
487 
488         return null;
489     }
490 
491     /**
492      * Sets up a custom tooltip when hovering over tree items.
493      * <p/>
494      * The tooltip will display the element's javadoc, if any, or the item's getText otherwise.
495      */
setupTooltip()496     private void setupTooltip() {
497         final Tree tree = (Tree) getControl();
498 
499         /*
500          * Reference:
501          * http://dev.eclipse.org/viewcvs/index.cgi/org.eclipse.swt.snippets/src/org/eclipse/swt/snippets/Snippet125.java?view=markup
502          */
503 
504         final Listener listener = new Listener() {
505             Shell tip = null;
506             Label label  = null;
507 
508             public void handleEvent(Event event) {
509                 switch(event.type) {
510                 case SWT.Dispose:
511                 case SWT.KeyDown:
512                 case SWT.MouseExit:
513                 case SWT.MouseDown:
514                 case SWT.MouseMove:
515                     if (tip != null) {
516                         tip.dispose();
517                         tip = null;
518                         label = null;
519                     }
520                     break;
521                 case SWT.MouseHover:
522                     if (tip != null) {
523                         tip.dispose();
524                         tip = null;
525                         label = null;
526                     }
527 
528                     String tooltip = null;
529 
530                     TreeItem item = tree.getItem(new Point(event.x, event.y));
531                     if (item != null) {
532                         Object data = item.getData();
533                         if (data instanceof UiElementTreeEditPart) {
534                             Object model = ((UiElementTreeEditPart) data).getModel();
535                             if (model instanceof UiElementNode) {
536                                 tooltip = ((UiElementNode) model).getDescriptor().getTooltip();
537                             }
538                         }
539 
540                         if (tooltip == null) {
541                             tooltip = item.getText();
542                         } else {
543                             tooltip = item.getText() + ":\r" + tooltip;
544                         }
545                     }
546 
547 
548                     if (tooltip != null) {
549                         Shell shell = tree.getShell();
550                         Display display = tree.getDisplay();
551 
552                         tip = new Shell(shell, SWT.ON_TOP | SWT.NO_FOCUS | SWT.TOOL);
553                         tip.setBackground(display .getSystemColor(SWT.COLOR_INFO_BACKGROUND));
554                         FillLayout layout = new FillLayout();
555                         layout.marginWidth = 2;
556                         tip.setLayout(layout);
557                         label = new Label(tip, SWT.NONE);
558                         label.setForeground(display.getSystemColor(SWT.COLOR_INFO_FOREGROUND));
559                         label.setBackground(display.getSystemColor(SWT.COLOR_INFO_BACKGROUND));
560                         label.setData("_TABLEITEM", item);
561                         label.setText(tooltip);
562                         label.addListener(SWT.MouseExit, this);
563                         label.addListener(SWT.MouseDown, this);
564                         Point size = tip.computeSize(SWT.DEFAULT, SWT.DEFAULT);
565                         Rectangle rect = item.getBounds(0);
566                         Point pt = tree.toDisplay(rect.x, rect.y);
567                         tip.setBounds(pt.x, pt.y, size.x, size.y);
568                         tip.setVisible(true);
569                     }
570                 }
571             }
572         };
573 
574         tree.addListener(SWT.Dispose, listener);
575         tree.addListener(SWT.KeyDown, listener);
576         tree.addListener(SWT.MouseMove, listener);
577         tree.addListener(SWT.MouseHover, listener);
578     }
579 
580     /**
581      * Sets up double-click action on the tree.
582      * <p/>
583      * By default, double-click (a.k.a. "default selection") on a valid list item will
584      * show the property view.
585      */
setupDoubleClick()586     private void setupDoubleClick() {
587         final Tree tree = (Tree) getControl();
588 
589         tree.addListener(SWT.DefaultSelection, new Listener() {
590             public void handleEvent(Event event) {
591                 EclipseUiHelper.showView(EclipseUiHelper.PROPERTY_SHEET_VIEW_ID,
592                         true /* activate */);
593             }
594         });
595     }
596 
597     // ---------------
598 
599     private class UiOutlineActions extends UiActions {
600 
601         @Override
getRootNode()602         protected UiDocumentNode getRootNode() {
603             return mEditor.getModel(); // this is LayoutEditor.getUiRootNode()
604         }
605 
606         // Select the new item
607         @Override
selectUiNode(UiElementNode uiNodeToSelect)608         protected void selectUiNode(UiElementNode uiNodeToSelect) {
609             setModelSelection(uiNodeToSelect);
610         }
611 
612         @Override
commitPendingXmlChanges()613         public void commitPendingXmlChanges() {
614             // Pass. There is nothing to commit before the XML is changed here.
615         }
616 
617     }
618 }
619