• Home
  • History
  • Annotate
  • 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.SdkConstants.ANDROID_LAYOUT_RESOURCE_PREFIX;
19 import static com.android.SdkConstants.ANDROID_URI;
20 import static com.android.SdkConstants.ATTR_CLASS;
21 import static com.android.SdkConstants.ATTR_NAME;
22 import static com.android.SdkConstants.LAYOUT_RESOURCE_PREFIX;
23 import static com.android.ide.eclipse.adt.internal.editors.layout.gle2.LayoutMetadata.KEY_FRAGMENT_LAYOUT;
24 
25 import com.android.annotations.NonNull;
26 import com.android.annotations.Nullable;
27 import com.android.ide.eclipse.adt.AdtPlugin;
28 import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate;
29 import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode;
30 import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
31 import com.android.ide.eclipse.adt.internal.resources.CyclicDependencyValidator;
32 import com.android.ide.eclipse.adt.internal.ui.ResourceChooser;
33 import com.android.resources.ResourceType;
34 import com.android.utils.Pair;
35 
36 import org.eclipse.core.resources.IFile;
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.window.Window;
46 import org.eclipse.swt.widgets.Menu;
47 import org.w3c.dom.Element;
48 import org.w3c.dom.Node;
49 
50 import java.util.ArrayList;
51 import java.util.List;
52 
53 /**
54  * Fragment context menu allowing a layout to be chosen for previewing in the fragment frame.
55  */
56 public class FragmentMenu extends SubmenuAction {
57     private static final String R_LAYOUT_RESOURCE_PREFIX = "R.layout."; //$NON-NLS-1$
58     private static final String ANDROID_R_PREFIX = "android.R.layout"; //$NON-NLS-1$
59 
60     /** Associated canvas */
61     private final LayoutCanvas mCanvas;
62 
63     /**
64      * Creates a "Preview Fragment" menu
65      *
66      * @param canvas associated canvas
67      */
FragmentMenu(LayoutCanvas canvas)68     public FragmentMenu(LayoutCanvas canvas) {
69         super("Fragment Layout");
70         mCanvas = canvas;
71     }
72 
73     @Override
addMenuItems(Menu menu)74     protected void addMenuItems(Menu menu) {
75         IAction action = new PickLayoutAction("Choose Layout...");
76         new ActionContributionItem(action).fill(menu, -1);
77 
78         SelectionManager selectionManager = mCanvas.getSelectionManager();
79         List<SelectionItem> selections = selectionManager.getSelections();
80         if (selections.size() == 0) {
81             return;
82         }
83 
84         SelectionItem first = selections.get(0);
85         UiViewElementNode node = first.getViewInfo().getUiViewNode();
86         if (node == null) {
87             return;
88         }
89         Element element = (Element) node.getXmlNode();
90 
91         String selected = getSelectedLayout();
92         if (selected != null) {
93             if (selected.startsWith(ANDROID_LAYOUT_RESOURCE_PREFIX)) {
94                 selected = selected.substring(ANDROID_LAYOUT_RESOURCE_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 
121                 if (source.contains("extends ListFragment")) { //$NON-NLS-1$
122                     layouts.add(Pair.of("list_content", //$NON-NLS-1$
123                             "@android:layout/list_content")); //$NON-NLS-1$
124                 }
125 
126                 int index = 0;
127                 while (true) {
128                     index = source.indexOf(R_LAYOUT_RESOURCE_PREFIX, index);
129                     if (index == -1) {
130                         break;
131                     } else {
132                         index += R_LAYOUT_RESOURCE_PREFIX.length();
133                         int end = index;
134                         while (end < source.length()) {
135                             char c = source.charAt(end);
136                             if (!Character.isJavaIdentifierPart(c)) {
137                                 break;
138                             }
139                             end++;
140                         }
141                         if (end > index) {
142                             String title = source.substring(index, end);
143                             String layout;
144                             // Is this R.layout part of an android.R.layout?
145                             int len = ANDROID_R_PREFIX.length() + 1; // prefix length to check
146                             if (index > len && source.startsWith(ANDROID_R_PREFIX, index - len)) {
147                                 layout = ANDROID_LAYOUT_RESOURCE_PREFIX + title;
148                             } else {
149                                 layout = LAYOUT_RESOURCE_PREFIX + title;
150                             }
151                             if (!self.equals(title)) {
152                                 layouts.add(Pair.of(title, layout));
153                             }
154                         }
155                     }
156 
157                     index++;
158                 }
159 
160                 if (layouts.size() > 0) {
161                     new Separator().fill(menu, -1);
162                     for (Pair<String, String> layout : layouts) {
163                         action = new SetFragmentLayoutAction(layout.getFirst(),
164                                 layout.getSecond(), selected);
165                         new ActionContributionItem(action).fill(menu, -1);
166                     }
167                 }
168             }
169         }
170 
171         if (selected != null) {
172             new Separator().fill(menu, -1);
173             action = new SetFragmentLayoutAction("Clear", null, null);
174             new ActionContributionItem(action).fill(menu, -1);
175         }
176     }
177 
178     /**
179      * Returns the class name of the fragment associated with the given {@code <fragment>}
180      * element.
181      *
182      * @param element the element for the fragment tag
183      * @return the fully qualified fragment class name, or null
184      */
185     @Nullable
getFragmentClass(@onNull Element element)186     public static String getFragmentClass(@NonNull Element element) {
187         String fqcn = element.getAttribute(ATTR_CLASS);
188         if (fqcn == null || fqcn.length() == 0) {
189             fqcn = element.getAttributeNS(ANDROID_URI, ATTR_NAME);
190         }
191         if (fqcn != null && fqcn.length() > 0) {
192             return fqcn;
193         } else {
194             return null;
195         }
196     }
197 
198     /**
199      * Returns the layout to be shown for the given {@code <fragment>} node.
200      *
201      * @param node the node corresponding to the {@code <fragment>} element
202      * @return the resource path to a layout to render for this fragment, or null
203      */
204     @Nullable
getFragmentLayout(@onNull Node node)205     public static String getFragmentLayout(@NonNull Node node) {
206         String layout = LayoutMetadata.getProperty(
207                 node, LayoutMetadata.KEY_FRAGMENT_LAYOUT);
208         if (layout != null) {
209             return layout;
210         }
211 
212         return null;
213     }
214 
215     /** Returns the name of the currently displayed layout in the fragment, or null */
216     @Nullable
getSelectedLayout()217     private String getSelectedLayout() {
218         SelectionManager selectionManager = mCanvas.getSelectionManager();
219         for (SelectionItem item : selectionManager.getSelections()) {
220             UiViewElementNode node = item.getViewInfo().getUiViewNode();
221             if (node != null) {
222                 String layout = getFragmentLayout(node.getXmlNode());
223                 if (layout != null) {
224                     return layout;
225                 }
226             }
227         }
228         return null;
229     }
230 
231     /**
232      * Set the given layout as the new fragment layout
233      *
234      * @param layout the layout resource name to show in this fragment
235      */
setNewLayout(@ullable String layout)236     public void setNewLayout(@Nullable String layout) {
237         LayoutEditorDelegate delegate = mCanvas.getEditorDelegate();
238         GraphicalEditorPart graphicalEditor = delegate.getGraphicalEditor();
239         SelectionManager selectionManager = mCanvas.getSelectionManager();
240 
241         for (SelectionItem item : selectionManager.getSnapshot()) {
242             UiViewElementNode node = item.getViewInfo().getUiViewNode();
243             if (node != null) {
244                 Node xmlNode = node.getXmlNode();
245                 LayoutMetadata.setProperty(delegate.getEditor(), xmlNode, KEY_FRAGMENT_LAYOUT,
246                         layout);
247             }
248         }
249 
250         // Refresh
251         graphicalEditor.recomputeLayout();
252         mCanvas.redraw();
253     }
254 
255     /** Action to set the given layout as the new layout in a fragment */
256     private class SetFragmentLayoutAction extends Action {
257         private final String mLayout;
258 
SetFragmentLayoutAction(String title, String layout, String selected)259         public SetFragmentLayoutAction(String title, String layout, String selected) {
260             super(title, IAction.AS_RADIO_BUTTON);
261             mLayout = layout;
262 
263             if (layout != null && layout.equals(selected)) {
264                 setChecked(true);
265             }
266         }
267 
268         @Override
run()269         public void run() {
270             if (isChecked()) {
271                 setNewLayout(mLayout);
272             }
273         }
274     }
275 
276     /**
277      * Action which brings up the "Create new XML File" wizard, pre-selected with the
278      * animation category
279      */
280     private class PickLayoutAction extends Action {
281 
PickLayoutAction(String title)282         public PickLayoutAction(String title) {
283             super(title, IAction.AS_PUSH_BUTTON);
284         }
285 
286         @Override
run()287         public void run() {
288             LayoutEditorDelegate delegate = mCanvas.getEditorDelegate();
289             IFile file = delegate.getEditor().getInputFile();
290             GraphicalEditorPart editor = delegate.getGraphicalEditor();
291             ResourceChooser dlg = ResourceChooser.create(editor, ResourceType.LAYOUT)
292                 .setInputValidator(CyclicDependencyValidator.create(file))
293                 .setInitialSize(85, 10)
294                 .setCurrentResource(getSelectedLayout());
295             int result = dlg.open();
296             if (result == ResourceChooser.CLEAR_RETURN_CODE) {
297                 setNewLayout(null);
298             } else if (result == Window.OK) {
299                 String newType = dlg.getCurrentResource();
300                 setNewLayout(newType);
301             }
302         }
303     }
304 }
305