• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011 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 package com.android.ide.eclipse.adt.internal.editors.layout.gle2;
17 
18 import static com.android.ide.common.layout.LayoutConstants.ANDROID_LAYOUT_PREFIX;
19 import static com.android.ide.common.layout.LayoutConstants.ANDROID_URI;
20 import static com.android.ide.common.layout.LayoutConstants.ATTR_CLASS;
21 import static com.android.ide.common.layout.LayoutConstants.ATTR_NAME;
22 import static com.android.ide.common.layout.LayoutConstants.LAYOUT_PREFIX;
23 import static com.android.ide.eclipse.adt.internal.editors.layout.gle2.LayoutMetadata.KEY_FRAGMENT_LAYOUT;
24 
25 import com.android.ide.common.resources.ResourceRepository;
26 import com.android.ide.eclipse.adt.AdtPlugin;
27 import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate;
28 import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode;
29 import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
30 import com.android.ide.eclipse.adt.internal.resources.CyclicDependencyValidator;
31 import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager;
32 import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
33 import com.android.ide.eclipse.adt.internal.ui.ResourceChooser;
34 import com.android.resources.ResourceType;
35 import com.android.util.Pair;
36 
37 import org.eclipse.core.resources.IProject;
38 import org.eclipse.core.runtime.CoreException;
39 import org.eclipse.jdt.core.IJavaProject;
40 import org.eclipse.jdt.core.IType;
41 import org.eclipse.jface.action.Action;
42 import org.eclipse.jface.action.ActionContributionItem;
43 import org.eclipse.jface.action.IAction;
44 import org.eclipse.jface.action.Separator;
45 import org.eclipse.jface.dialogs.IInputValidator;
46 import org.eclipse.jface.text.IDocument;
47 import org.eclipse.jface.window.Window;
48 import org.eclipse.swt.widgets.Menu;
49 import org.eclipse.swt.widgets.Shell;
50 import org.w3c.dom.Element;
51 import org.w3c.dom.Node;
52 
53 import java.util.ArrayList;
54 import java.util.List;
55 
56 /**
57  * Fragment context menu allowing a layout to be chosen for previewing in the fragment frame.
58  */
59 public class FragmentMenu extends SubmenuAction {
60     private static final String R_LAYOUT_PREFIX = "R.layout."; //$NON-NLS-1$
61     private static final String ANDROID_R_PREFIX = "android.R"; //$NON-NLS-1$
62 
63     /** Associated canvas */
64     private final LayoutCanvas mCanvas;
65 
66     /**
67      * Creates a "Preview Fragment" menu
68      *
69      * @param canvas associated canvas
70      */
FragmentMenu(LayoutCanvas canvas)71     public FragmentMenu(LayoutCanvas canvas) {
72         super("Fragment Layout");
73         mCanvas = canvas;
74     }
75 
76     @Override
addMenuItems(Menu menu)77     protected void addMenuItems(Menu menu) {
78         IAction action = new PickLayoutAction("Choose Layout...");
79         new ActionContributionItem(action).fill(menu, -1);
80 
81         SelectionManager selectionManager = mCanvas.getSelectionManager();
82         List<SelectionItem> selections = selectionManager.getSelections();
83         if (selections.size() == 0) {
84             return;
85         }
86 
87         SelectionItem first = selections.get(0);
88         UiViewElementNode node = first.getViewInfo().getUiViewNode();
89         Element element = (Element) node.getXmlNode();
90 
91         String selected = getSelectedLayout();
92         if (selected != null) {
93             if (selected.startsWith(ANDROID_LAYOUT_PREFIX)) {
94                 selected = selected.substring(ANDROID_LAYOUT_PREFIX.length());
95             }
96         }
97 
98         String fqcn = getFragmentClass(element);
99         if (fqcn != null) {
100             // Look up the corresponding activity class and try to figure out
101             // which layouts it is referring to and list these here as reasonable
102             // guesses
103             IProject project = mCanvas.getEditorDelegate().getEditor().getProject();
104             String source = null;
105             try {
106                 IJavaProject javaProject = BaseProjectHelper.getJavaProject(project);
107                 IType type = javaProject.findType(fqcn);
108                 if (type != null) {
109                     source = type.getSource();
110                 }
111             } catch (CoreException e) {
112                 AdtPlugin.log(e, null);
113             }
114             // Find layouts. This is based on just skimming the Fragment class and looking
115             // for layout references of the form R.layout.*.
116             if (source != null) {
117                 String self = mCanvas.getLayoutResourceName();
118                 // Pair of <title,layout> to be displayed to the user
119                 List<Pair<String, String>> layouts = new ArrayList<Pair<String, String>>();
120                 int index = 0;
121                 while (true) {
122                     index = source.indexOf(R_LAYOUT_PREFIX, index);
123                     if (index == -1) {
124                         break;
125                     } else {
126                         index += R_LAYOUT_PREFIX.length();
127                         int end = index;
128                         while (end < source.length()) {
129                             char c = source.charAt(end);
130                             if (!Character.isJavaIdentifierPart(c)) {
131                                 break;
132                             }
133                             end++;
134                         }
135                         if (end > index) {
136                             String title = source.substring(index, end);
137                             String layout;
138                             // Is this R.layout part of an android.R.layout?
139                             int len = ANDROID_R_PREFIX.length() + 1; // prefix length to check
140                             if (index > len && source.startsWith(ANDROID_R_PREFIX, index - len)) {
141                                 layout = ANDROID_LAYOUT_PREFIX + title;
142                             } else {
143                                 layout = LAYOUT_PREFIX + title;
144                             }
145                             if (!self.equals(title)) {
146                                 layouts.add(Pair.of(title, layout));
147                             }
148                         }
149                     }
150 
151                     index++;
152                 }
153 
154                 if (layouts.size() > 0) {
155                     new Separator().fill(menu, -1);
156                     for (Pair<String, String> layout : layouts) {
157                         action = new SetFragmentLayoutAction(layout.getFirst(),
158                                 layout.getSecond(), selected);
159                         new ActionContributionItem(action).fill(menu, -1);
160                     }
161                 }
162             }
163         }
164 
165         if (selected != null) {
166             new Separator().fill(menu, -1);
167             action = new SetFragmentLayoutAction("Clear", null, null);
168             new ActionContributionItem(action).fill(menu, -1);
169         }
170     }
171 
172     /**
173      * Returns the class name of the fragment associated with the given {@code <fragment>}
174      * element.
175      *
176      * @param element the element for the fragment tag
177      * @return the fully qualified fragment class name, or null
178      */
getFragmentClass(Element element)179     public static String getFragmentClass(Element element) {
180         String fqcn = element.getAttribute(ATTR_CLASS);
181         if (fqcn == null || fqcn.length() == 0) {
182             fqcn = element.getAttributeNS(ANDROID_URI, ATTR_NAME);
183         }
184         if (fqcn != null && fqcn.length() > 0) {
185             return fqcn;
186         } else {
187             return null;
188         }
189     }
190 
191     /**
192      * Returns the layout to be shown for the given {@code <fragment>} node.
193      *
194      * @param node the node corresponding to the {@code <fragment>} element
195      * @return the resource path to a layout to render for this fragment, or null
196      */
getFragmentLayout(Node node)197     public static String getFragmentLayout(Node node) {
198         LayoutMetadata metadata = LayoutMetadata.get();
199         String layout = metadata.getProperty((IDocument) null, node,
200                 LayoutMetadata.KEY_FRAGMENT_LAYOUT);
201         if (layout != null) {
202             return layout;
203         }
204 
205         return null;
206     }
207 
208     /** Returns the name of the currently displayed layout in the fragment, or null */
getSelectedLayout()209     private String getSelectedLayout() {
210         SelectionManager selectionManager = mCanvas.getSelectionManager();
211         for (SelectionItem item : selectionManager.getSelections()) {
212             UiViewElementNode node = item.getViewInfo().getUiViewNode();
213             String layout = getFragmentLayout(node.getXmlNode());
214             if (layout != null) {
215                 return layout;
216             }
217         }
218         return null;
219     }
220 
221     /**
222      * Set the given layout as the new fragment layout
223      *
224      * @param layout the layout resource name to show in this fragment
225      */
setNewLayout(String layout)226     public void setNewLayout(String layout) {
227         LayoutEditorDelegate delegate = mCanvas.getEditorDelegate();
228         GraphicalEditorPart graphicalEditor = delegate.getGraphicalEditor();
229         LayoutMetadata metadata = LayoutMetadata.get();
230         SelectionManager selectionManager = mCanvas.getSelectionManager();
231 
232         for (SelectionItem item : selectionManager.getSelections()) {
233             UiViewElementNode node = item.getViewInfo().getUiViewNode();
234             Node xmlNode = node.getXmlNode();
235             metadata.setProperty(delegate.getEditor(), xmlNode, KEY_FRAGMENT_LAYOUT, layout);
236         }
237 
238         // Refresh
239         graphicalEditor.recomputeLayout();
240         mCanvas.redraw();
241     }
242 
243     /** Action to set the given layout as the new layout in a fragment */
244     private class SetFragmentLayoutAction extends Action {
245         private final String mLayout;
246 
SetFragmentLayoutAction(String title, String layout, String selected)247         public SetFragmentLayoutAction(String title, String layout, String selected) {
248             super(title, IAction.AS_RADIO_BUTTON);
249             mLayout = layout;
250 
251             if (layout != null && layout.equals(selected)) {
252                 setChecked(true);
253             }
254         }
255 
256         @Override
run()257         public void run() {
258             setNewLayout(mLayout);
259         }
260     }
261 
262     /**
263      * Action which brings up the "Create new XML File" wizard, pre-selected with the
264      * animation category
265      */
266     private class PickLayoutAction extends Action {
267 
PickLayoutAction(String title)268         public PickLayoutAction(String title) {
269             super(title, IAction.AS_PUSH_BUTTON);
270         }
271 
272         @Override
run()273         public void run() {
274             LayoutEditorDelegate delegate = mCanvas.getEditorDelegate();
275             IProject project = delegate.getEditor().getProject();
276             // get the resource repository for this project and the system resources.
277             ResourceRepository projectRepository = ResourceManager.getInstance()
278                     .getProjectResources(project);
279             Shell shell = mCanvas.getShell();
280 
281             AndroidTargetData data = delegate.getEditor().getTargetData();
282             ResourceRepository systemRepository = data.getFrameworkResources();
283 
284             ResourceChooser dlg = new ResourceChooser(project,
285                     ResourceType.LAYOUT, projectRepository,
286                     systemRepository, shell);
287 
288             IInputValidator validator =
289                 CyclicDependencyValidator.create(delegate.getEditor().getInputFile());
290 
291             if (validator != null) {
292                 // Ensure wide enough to accommodate validator error message
293                 dlg.setSize(85, 10);
294                 dlg.setInputValidator(validator);
295             }
296 
297             String layout = getSelectedLayout();
298             if (layout != null) {
299                 dlg.setCurrentResource(layout);
300             }
301 
302             int result = dlg.open();
303             if (result == ResourceChooser.CLEAR_RETURN_CODE) {
304                 setNewLayout(null);
305             } else if (result == Window.OK) {
306                 String newType = dlg.getCurrentResource();
307                 setNewLayout(newType);
308             }
309         }
310     }
311 }
312