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 */ 68 public FragmentMenu(LayoutCanvas canvas) { 69 super("Fragment Layout"); 70 mCanvas = canvas; 71 } 72 73 @Override 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 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 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 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 */ 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 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 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 282 public PickLayoutAction(String title) { 283 super(title, IAction.AS_PUSH_BUTTON); 284 } 285 286 @Override 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