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