1 /* 2 * Copyright (C) 2007 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 17 package com.android.ide.eclipse.adt.internal.editors.manifest.model; 18 19 import com.android.ide.eclipse.adt.AdtPlugin; 20 import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor; 21 import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor; 22 import com.android.ide.eclipse.adt.internal.editors.descriptors.TextAttributeDescriptor; 23 import com.android.ide.eclipse.adt.internal.editors.ui.SectionHelper; 24 import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode; 25 import com.android.ide.eclipse.adt.internal.editors.uimodel.UiTextAttributeNode; 26 27 import org.eclipse.core.resources.IFile; 28 import org.eclipse.core.resources.IProject; 29 import org.eclipse.core.runtime.IStatus; 30 import org.eclipse.jdt.core.IClasspathEntry; 31 import org.eclipse.jdt.core.IJavaElement; 32 import org.eclipse.jdt.core.IJavaProject; 33 import org.eclipse.jdt.core.IPackageFragment; 34 import org.eclipse.jdt.core.IPackageFragmentRoot; 35 import org.eclipse.jdt.core.JavaCore; 36 import org.eclipse.jdt.core.JavaModelException; 37 import org.eclipse.jdt.ui.JavaUI; 38 import org.eclipse.jdt.ui.actions.OpenNewPackageWizardAction; 39 import org.eclipse.jdt.ui.actions.ShowInPackageViewAction; 40 import org.eclipse.jface.dialogs.IMessageProvider; 41 import org.eclipse.jface.viewers.StructuredSelection; 42 import org.eclipse.jface.window.Window; 43 import org.eclipse.swt.SWT; 44 import org.eclipse.swt.events.DisposeEvent; 45 import org.eclipse.swt.events.DisposeListener; 46 import org.eclipse.swt.events.ModifyEvent; 47 import org.eclipse.swt.events.ModifyListener; 48 import org.eclipse.swt.events.SelectionAdapter; 49 import org.eclipse.swt.events.SelectionEvent; 50 import org.eclipse.swt.layout.GridData; 51 import org.eclipse.swt.layout.GridLayout; 52 import org.eclipse.swt.widgets.Button; 53 import org.eclipse.swt.widgets.Composite; 54 import org.eclipse.swt.widgets.Text; 55 import org.eclipse.ui.IEditorInput; 56 import org.eclipse.ui.IFileEditorInput; 57 import org.eclipse.ui.IWorkbenchPartSite; 58 import org.eclipse.ui.dialogs.SelectionDialog; 59 import org.eclipse.ui.forms.IManagedForm; 60 import org.eclipse.ui.forms.events.HyperlinkAdapter; 61 import org.eclipse.ui.forms.events.HyperlinkEvent; 62 import org.eclipse.ui.forms.widgets.FormText; 63 import org.eclipse.ui.forms.widgets.FormToolkit; 64 import org.eclipse.ui.forms.widgets.TableWrapData; 65 66 import java.util.ArrayList; 67 68 /** 69 * Represents an XML attribute for a package, that can be modified using a simple text field or 70 * a dialog to choose an existing package. Also, there's a link to create a new package. 71 * <p/> 72 * See {@link UiTextAttributeNode} for more information. 73 */ 74 public class UiPackageAttributeNode extends UiTextAttributeNode { 75 76 /** 77 * Creates a {@link UiPackageAttributeNode} object that will display ui to select or create 78 * a package. 79 * @param attributeDescriptor the {@link AttributeDescriptor} object linked to the Ui Node. 80 */ 81 public UiPackageAttributeNode(AttributeDescriptor attributeDescriptor, UiElementNode uiParent) { 82 super(attributeDescriptor, uiParent); 83 } 84 85 /* (non-java doc) 86 * Creates a label widget and an associated text field. 87 * <p/> 88 * As most other parts of the android manifest editor, this assumes the 89 * parent uses a table layout with 2 columns. 90 */ 91 @Override 92 public void createUiControl(final Composite parent, final IManagedForm managedForm) { 93 setManagedForm(managedForm); 94 FormToolkit toolkit = managedForm.getToolkit(); 95 TextAttributeDescriptor desc = (TextAttributeDescriptor) getDescriptor(); 96 97 StringBuilder label = new StringBuilder(); 98 label.append("<form><p><a href='unused'>"); //$NON-NLS-1$ 99 label.append(desc.getUiName()); 100 label.append("</a></p></form>"); //$NON-NLS-1$ 101 FormText formText = SectionHelper.createFormText(parent, toolkit, true /* isHtml */, 102 label.toString(), true /* setupLayoutData */); 103 formText.addHyperlinkListener(new HyperlinkAdapter() { 104 @Override 105 public void linkActivated(HyperlinkEvent e) { 106 super.linkActivated(e); 107 doLabelClick(); 108 } 109 }); 110 formText.setLayoutData(new TableWrapData(TableWrapData.LEFT, TableWrapData.MIDDLE)); 111 SectionHelper.addControlTooltip(formText, desc.getTooltip()); 112 113 Composite composite = toolkit.createComposite(parent); 114 composite.setLayoutData(new TableWrapData(TableWrapData.FILL_GRAB, TableWrapData.MIDDLE)); 115 GridLayout gl = new GridLayout(2, false); 116 gl.marginHeight = gl.marginWidth = 0; 117 composite.setLayout(gl); 118 // Fixes missing text borders under GTK... also requires adding a 1-pixel margin 119 // for the text field below 120 toolkit.paintBordersFor(composite); 121 122 final Text text = toolkit.createText(composite, getCurrentValue()); 123 GridData gd = new GridData(GridData.FILL_HORIZONTAL); 124 gd.horizontalIndent = 1; // Needed by the fixed composite borders under GTK 125 text.setLayoutData(gd); 126 127 setTextWidget(text); 128 129 Button browseButton = toolkit.createButton(composite, "Browse...", SWT.PUSH); 130 131 browseButton.addSelectionListener(new SelectionAdapter() { 132 @Override 133 public void widgetSelected(SelectionEvent e) { 134 super.widgetSelected(e); 135 doBrowseClick(); 136 } 137 }); 138 139 } 140 141 /* (non-java doc) 142 * Adds a validator to the text field that calls managedForm.getMessageManager(). 143 */ 144 @Override 145 protected void onAddValidators(final Text text) { 146 ModifyListener listener = new ModifyListener() { 147 @Override 148 public void modifyText(ModifyEvent e) { 149 String package_name = text.getText(); 150 if (package_name.indexOf('.') < 1) { 151 getManagedForm().getMessageManager().addMessage(text, 152 "Package name should contain at least two identifiers.", 153 null /* data */, IMessageProvider.ERROR, text); 154 } else { 155 getManagedForm().getMessageManager().removeMessage(text, text); 156 } 157 } 158 }; 159 160 text.addModifyListener(listener); 161 162 // Make sure the validator removes its message(s) when the widget is disposed 163 text.addDisposeListener(new DisposeListener() { 164 @Override 165 public void widgetDisposed(DisposeEvent e) { 166 getManagedForm().getMessageManager().removeMessage(text, text); 167 } 168 }); 169 170 // Finally call the validator once to make sure the initial value is processed 171 listener.modifyText(null); 172 } 173 174 /** 175 * Handles response to the Browse button by creating a Package dialog. 176 * */ 177 private void doBrowseClick() { 178 Text text = getTextWidget(); 179 180 // we need to get the project of the manifest. 181 IProject project = getProject(); 182 if (project != null) { 183 184 try { 185 SelectionDialog dlg = JavaUI.createPackageDialog(text.getShell(), 186 JavaCore.create(project), 0); 187 dlg.setTitle("Select Android Package"); 188 dlg.setMessage("Select the package for the Android project."); 189 SelectionDialog.setDefaultImage(AdtPlugin.getAndroidLogo()); 190 191 if (dlg.open() == Window.OK) { 192 Object[] results = dlg.getResult(); 193 if (results.length == 1) { 194 setPackageTextField((IPackageFragment)results[0]); 195 } 196 } 197 } catch (JavaModelException e1) { 198 } 199 } 200 } 201 202 /** 203 * Handles response to the Label hyper link being activated. 204 */ 205 private void doLabelClick() { 206 // get the current package name 207 String package_name = getTextWidget().getText().trim(); 208 209 if (package_name.length() == 0) { 210 createNewPackage(); 211 } else { 212 // Try to select the package in the Package Explorer for the current 213 // project and the current editor's site. 214 215 IProject project = getProject(); 216 if (project == null) { 217 AdtPlugin.log(IStatus.ERROR, "Failed to get project for UiPackageAttribute"); //$NON-NLS-1$ 218 return; 219 } 220 221 IWorkbenchPartSite site = getUiParent().getEditor().getSite(); 222 if (site == null) { 223 AdtPlugin.log(IStatus.ERROR, "Failed to get editor site for UiPackageAttribute"); //$NON-NLS-1$ 224 return; 225 } 226 227 for (IPackageFragmentRoot root : getPackageFragmentRoots(project)) { 228 IPackageFragment fragment = root.getPackageFragment(package_name); 229 if (fragment != null && fragment.exists()) { 230 ShowInPackageViewAction action = new ShowInPackageViewAction(site); 231 action.run(fragment); 232 // This action's run() doesn't provide the status (although internally it could) 233 // so we just assume it worked. 234 return; 235 } 236 } 237 } 238 } 239 240 /** 241 * Utility method that returns the project for the current file being edited. 242 * 243 * @return The IProject for the current file being edited or null. 244 */ 245 private IProject getProject() { 246 UiElementNode uiNode = getUiParent(); 247 AndroidXmlEditor editor = uiNode.getEditor(); 248 IEditorInput input = editor.getEditorInput(); 249 if (input instanceof IFileEditorInput) { 250 // from the file editor we can get the IFile object, and from it, the IProject. 251 IFile file = ((IFileEditorInput)input).getFile(); 252 return file.getProject(); 253 } 254 255 return null; 256 } 257 258 /** 259 * Utility method that computes and returns the list of {@link IPackageFragmentRoot} 260 * corresponding to the source folder of the specified project. 261 * 262 * @param project the project 263 * @return an array of IPackageFragmentRoot. Can be empty but not null. 264 */ 265 private IPackageFragmentRoot[] getPackageFragmentRoots(IProject project) { 266 ArrayList<IPackageFragmentRoot> result = new ArrayList<IPackageFragmentRoot>(); 267 try { 268 IJavaProject javaProject = JavaCore.create(project); 269 IPackageFragmentRoot[] roots = javaProject.getPackageFragmentRoots(); 270 for (int i = 0; i < roots.length; i++) { 271 IClasspathEntry entry = roots[i].getRawClasspathEntry(); 272 if (entry.getEntryKind() == IClasspathEntry.CPE_SOURCE) { 273 result.add(roots[i]); 274 } 275 } 276 } catch (JavaModelException e) { 277 } 278 279 return result.toArray(new IPackageFragmentRoot[result.size()]); 280 } 281 282 /** 283 * Utility method that sets the package's text field to the package fragment's name. 284 * */ 285 private void setPackageTextField(IPackageFragment type) { 286 Text text = getTextWidget(); 287 288 String name = type.getElementName(); 289 290 text.setText(name); 291 } 292 293 294 /** 295 * Displays and handles a "Create Package Wizard". 296 * 297 * This is invoked by doLabelClick() when clicking on the hyperlink label with an 298 * empty package text field. 299 */ 300 private void createNewPackage() { 301 OpenNewPackageWizardAction action = new OpenNewPackageWizardAction(); 302 303 IProject project = getProject(); 304 action.setSelection(new StructuredSelection(project)); 305 action.run(); 306 307 IJavaElement element = action.getCreatedElement(); 308 if (element != null && 309 element.exists() && 310 element.getElementType() == IJavaElement.PACKAGE_FRAGMENT) { 311 setPackageTextField((IPackageFragment) element); 312 } 313 } 314 315 @Override 316 public String[] getPossibleValues(String prefix) { 317 // TODO: compute a list of existing packages for content assist completion 318 return null; 319 } 320 } 321 322