OSDN Git Service

original
[gb-231r1-is01/Gingerbread_2.3.3_r1_IS01.git] / sdk / eclipse / plugins / com.android.ide.eclipse.adt / src / com / android / ide / eclipse / adt / internal / editors / layout / gle1 / UiContentOutlinePage.java
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.gle1;
19
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;
33
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;
63
64 import java.util.ArrayList;
65 import java.util.Iterator;
66 import java.util.LinkedList;
67 import java.util.List;
68
69 /**
70  * Implementation of the {@link ContentOutlinePage} to display {@link UiElementNode}.
71  *
72  * @since GLE1
73  */
74 public class UiContentOutlinePage extends ContentOutlinePage {
75
76     private GraphicalLayoutEditor mEditor;
77
78     private Action mAddAction;
79     private Action mDeleteAction;
80     private Action mUpAction;
81     private Action mDownAction;
82
83     private UiOutlineActions mUiActions = new UiOutlineActions();
84
85     public UiContentOutlinePage(GraphicalLayoutEditor editor, final EditPartViewer viewer) {
86         super(viewer);
87         mEditor = editor;
88         IconFactory factory = IconFactory.getInstance();
89
90         mAddAction = new Action("Add...") {
91             @Override
92             public void run() {
93                 List<UiElementNode> nodes = getModelSelections();
94                 UiElementNode node = nodes != null && nodes.size() > 0 ? nodes.get(0) : null;
95
96                 mUiActions.doAdd(node, viewer.getControl().getShell());
97             }
98         };
99         mAddAction.setToolTipText("Adds a new element.");
100         mAddAction.setImageDescriptor(factory.getImageDescriptor("add")); //$NON-NLS-1$
101
102         mDeleteAction = new Action("Remove...") {
103             @Override
104             public void run() {
105                 List<UiElementNode> nodes = getModelSelections();
106
107                 mUiActions.doRemove(nodes, viewer.getControl().getShell());
108             }
109         };
110         mDeleteAction.setToolTipText("Removes an existing selected element.");
111         mDeleteAction.setImageDescriptor(factory.getImageDescriptor("delete")); //$NON-NLS-1$
112
113         mUpAction = new Action("Up") {
114             @Override
115             public void run() {
116                 List<UiElementNode> nodes = getModelSelections();
117
118                 mUiActions.doUp(nodes);
119             }
120         };
121         mUpAction.setToolTipText("Moves the selected element up");
122         mUpAction.setImageDescriptor(factory.getImageDescriptor("up")); //$NON-NLS-1$
123
124         mDownAction = new Action("Down") {
125             @Override
126             public void run() {
127                 List<UiElementNode> nodes = getModelSelections();
128
129                 mUiActions.doDown(nodes);
130             }
131         };
132         mDownAction.setToolTipText("Moves the selected element down");
133         mDownAction.setImageDescriptor(factory.getImageDescriptor("down")); //$NON-NLS-1$
134
135         // all actions disabled by default.
136         mAddAction.setEnabled(false);
137         mDeleteAction.setEnabled(false);
138         mUpAction.setEnabled(false);
139         mDownAction.setEnabled(false);
140
141         addSelectionChangedListener(new ISelectionChangedListener() {
142             public void selectionChanged(SelectionChangedEvent event) {
143                 ISelection selection = event.getSelection();
144
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;
149
150                     if (structSel.size() == 1 &&
151                             structSel.getFirstElement() instanceof UiDocumentTreeEditPart) {
152                         mDeleteAction.setEnabled(false);
153                         mUpAction.setEnabled(false);
154                         mDownAction.setEnabled(false);
155                     } else {
156                         mDeleteAction.setEnabled(true);
157                         mUpAction.setEnabled(true);
158                         mDownAction.setEnabled(true);
159                     }
160
161                     // the "add" button is always enabled, in order to be able to set the
162                     // initial root node
163                     mAddAction.setEnabled(true);
164                 }
165             }
166         });
167     }
168
169
170     /* (non-Javadoc)
171      * @see org.eclipse.ui.part.IPage#createControl(org.eclipse.swt.widgets.Composite)
172      */
173     @Override
174     public void createControl(Composite parent) {
175         // create outline viewer page
176         getViewer().createControl(parent);
177
178         // configure outline viewer
179         getViewer().setEditPartFactory(new UiElementTreeEditPartFactory());
180
181         setupOutline();
182         setupContextMenu();
183         setupTooltip();
184         setupDoubleClick();
185     }
186
187     /*
188      * (non-Javadoc)
189      * @see org.eclipse.ui.part.Page#setActionBars(org.eclipse.ui.IActionBars)
190      *
191      * Called automatically after createControl
192      */
193     @Override
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);
201
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);
208     }
209
210     /* (non-Javadoc)
211      * @see org.eclipse.ui.part.IPage#dispose()
212      */
213     @Override
214     public void dispose() {
215         breakConnectionWithEditor();
216
217         // dispose
218         super.dispose();
219     }
220
221     /* (non-Javadoc)
222      * @see org.eclipse.ui.part.IPage#getControl()
223      */
224     @Override
225     public Control getControl() {
226         return getViewer().getControl();
227     }
228
229     void setNewEditor(GraphicalLayoutEditor editor) {
230         mEditor = editor;
231         setupOutline();
232     }
233
234     void breakConnectionWithEditor() {
235         // unhook outline viewer
236         mEditor.getSelectionSynchronizer().removeViewer(getViewer());
237     }
238
239     private void setupOutline() {
240
241         getViewer().setEditDomain(mEditor.getEditDomain());
242
243         // hook outline viewer
244         mEditor.getSelectionSynchronizer().addViewer(getViewer());
245
246         // initialize outline viewer with model
247         getViewer().setContents(mEditor.getModel());
248     }
249
250     private void setupContextMenu() {
251         MenuManager menuManager = new MenuManager();
252         menuManager.setRemoveAllWhenShown(true);
253         menuManager.addMenuListener(new IMenuListener() {
254             /**
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.
259              */
260            public void menuAboutToShow(IMenuManager manager) {
261                List<UiElementNode> selected = getModelSelections();
262
263                if (selected != null) {
264                    doCreateMenuAction(manager, selected);
265                    return;
266                }
267                doCreateMenuAction(manager, null /* ui_node */);
268             }
269         });
270         Control control = getControl();
271         Menu contextMenu = menuManager.createContextMenu(control);
272         control.setMenu(contextMenu);
273     }
274
275     /**
276      * Adds the menu actions to the context menu when the given UI node is selected in
277      * the tree view.
278      *
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
281      *                is to be modified.
282      */
283     private void doCreateMenuAction(IMenuManager manager, List<UiElementNode> selected) {
284
285         if (selected != null) {
286             boolean hasXml = false;
287             for (UiElementNode uiNode : selected) {
288                 if (uiNode.getXmlNode() != null) {
289                     hasXml = true;
290                     break;
291                 }
292             }
293
294             if (hasXml) {
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 */));
299
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(),
311                                 selected.get(0)));
312                     }
313                 }
314                 manager.add(new Separator());
315             }
316         }
317
318         // Append "add" and "remove" actions. They do the same thing as the add/remove
319         // buttons on the side.
320         //
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);
325         }
326
327         if (selected != null) {
328             manager.add(mDeleteAction);
329             manager.add(new Separator());
330
331             manager.add(mUpAction);
332             manager.add(mDownAction);
333         }
334
335         if (selected != null && selected.size() == 1) {
336             manager.add(new Separator());
337
338             Action propertiesAction = new Action("Properties") {
339                 @Override
340                 public void run() {
341                     EclipseUiHelper.showView(EclipseUiHelper.PROPERTY_SHEET_VIEW_ID,
342                             true /* activate */);
343                 }
344             };
345             propertiesAction.setToolTipText("Displays properties of the selected element.");
346             manager.add(propertiesAction);
347         }
348     }
349
350     /**
351      * Updates the outline view with the model of the {@link IGraphicalLayoutEditor}.
352      * <p/>
353      * This attemps to preserve the selection, if any.
354      */
355     public void reloadModel() {
356         // Attemps to preserve the UiNode selection, if any
357         List<UiElementNode> uiNodes = null;
358         try {
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();
362
363             // perform the update
364             getViewer().setContents(mEditor.getModel());
365
366         } finally {
367             // restore selection
368             if (uiNodes != null) {
369                 setModelSelection(uiNodes.get(0));
370             }
371         }
372     }
373
374     /**
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.
378      * <p/>
379      * When there is no actual selection, this might still return the root node,
380      * which is of type {@link UiDocumentTreeEditPart}.
381      */
382     @SuppressWarnings("unchecked")
383     private List<UiElementTreeEditPart> getViewerSelections() {
384         ISelection selection = getSelection();
385         if (selection instanceof StructuredSelection) {
386             StructuredSelection structuredSelection = (StructuredSelection)selection;
387
388             if (structuredSelection.size() > 0) {
389                 ArrayList<UiElementTreeEditPart> selected = new ArrayList<UiElementTreeEditPart>();
390
391                 for (Iterator it = structuredSelection.iterator(); it.hasNext(); ) {
392                     Object selectedObj = it.next();
393
394                     if (selectedObj instanceof UiElementTreeEditPart) {
395                         selected.add((UiElementTreeEditPart) selectedObj);
396                     }
397                 }
398
399                 return selected.size() > 0 ? selected : null;
400             }
401         }
402
403         return null;
404     }
405
406     /**
407      * Returns the currently selected model element, which is either an
408      * {@link UiViewTreeEditPart} or an {@link UiLayoutTreeEditPart}.
409      * <p/>
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.)
412      */
413     private List<UiElementNode> getModelSelections() {
414
415         List<UiElementTreeEditPart> parts = getViewerSelections();
416
417         if (parts != null) {
418             ArrayList<UiElementNode> selected = new ArrayList<UiElementNode>();
419
420             for (UiElementTreeEditPart part : parts) {
421                 if (part instanceof UiViewTreeEditPart || part instanceof UiLayoutTreeEditPart) {
422                     selected.add((UiElementNode) part.getModel());
423                 }
424             }
425
426             return selected.size() > 0 ? selected : null;
427         }
428
429         return null;
430     }
431
432     /**
433      * Selects the corresponding edit part in the tree viewer.
434      */
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);
442             }
443             setSelection(new TreeSelection(new TreePath(segments.toArray())));
444         }
445     }
446
447     /**
448      * Selects the corresponding model element in the tree viewer.
449      */
450     private void setModelSelection(UiElementNode uiNodeToSelect) {
451         if (uiNodeToSelect != null) {
452
453             // find an edit part that has the requested model element
454             UiElementTreeEditPart part = findPartForModel(
455                     (UiElementTreeEditPart) getViewer().getContents(),
456                     uiNodeToSelect);
457
458             // if we found a part, select it and reveal it
459             if (part != null) {
460                 setViewerSelection(part);
461                 getViewer().reveal(part);
462             }
463         }
464     }
465
466     /**
467      * Utility method that tries to find an edit part that matches a given model UI node.
468      *
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.
472      */
473     private UiElementTreeEditPart findPartForModel(UiElementTreeEditPart rootPart,
474             UiElementNode uiNode) {
475         if (rootPart.getModel() == uiNode) {
476             return rootPart;
477         }
478
479         for (Object part : rootPart.getChildren()) {
480             if (part instanceof UiElementTreeEditPart) {
481                 UiElementTreeEditPart found = findPartForModel(
482                         (UiElementTreeEditPart) part, uiNode);
483                 if (found != null) {
484                     return found;
485                 }
486             }
487         }
488
489         return null;
490     }
491
492     /**
493      * Sets up a custom tooltip when hovering over tree items.
494      * <p/>
495      * The tooltip will display the element's javadoc, if any, or the item's getText otherwise.
496      */
497     private void setupTooltip() {
498         final Tree tree = (Tree) getControl();
499
500         /*
501          * Reference:
502          * http://dev.eclipse.org/viewcvs/index.cgi/org.eclipse.swt.snippets/src/org/eclipse/swt/snippets/Snippet125.java?view=markup
503          */
504
505         final Listener listener = new Listener() {
506             Shell tip = null;
507             Label label  = null;
508
509             public void handleEvent(Event event) {
510                 switch(event.type) {
511                 case SWT.Dispose:
512                 case SWT.KeyDown:
513                 case SWT.MouseExit:
514                 case SWT.MouseDown:
515                 case SWT.MouseMove:
516                     if (tip != null) {
517                         tip.dispose();
518                         tip = null;
519                         label = null;
520                     }
521                     break;
522                 case SWT.MouseHover:
523                     if (tip != null) {
524                         tip.dispose();
525                         tip = null;
526                         label = null;
527                     }
528
529                     String tooltip = null;
530
531                     TreeItem item = tree.getItem(new Point(event.x, event.y));
532                     if (item != null) {
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();
538                             }
539                         }
540
541                         if (tooltip == null) {
542                             tooltip = item.getText();
543                         } else {
544                             tooltip = item.getText() + ":\r" + tooltip;
545                         }
546
547                         if (tooltip != null) {
548                             Shell shell = tree.getShell();
549                             Display display = tree.getDisplay();
550
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);
568                         }
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      */
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
602         protected UiDocumentNode getRootNode() {
603             return mEditor.getModel(); // this is LayoutEditor.getUiRootNode()
604         }
605
606         // Select the new item
607         @Override
608         protected void selectUiNode(UiElementNode uiNodeToSelect) {
609             setModelSelection(uiNodeToSelect);
610         }
611
612         @Override
613         public void commitPendingXmlChanges() {
614             // Pass. There is nothing to commit before the XML is changed here.
615         }
616
617     }
618 }