1 /* 2 * Copyright (C) 2009 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.common.xml.ManifestData; 20 import com.android.ide.eclipse.adt.AdtPlugin; 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.manifest.ManifestEditor; 24 import com.android.ide.eclipse.adt.internal.editors.ui.SectionHelper; 25 import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode; 26 import com.android.ide.eclipse.adt.internal.editors.uimodel.UiTextAttributeNode; 27 import com.android.ide.eclipse.adt.internal.project.AndroidManifestHelper; 28 import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper; 29 import com.android.ide.eclipse.adt.internal.project.ProjectHelper; 30 import com.android.ide.eclipse.adt.internal.wizards.actions.NewProjectAction; 31 import com.android.ide.eclipse.adt.internal.wizards.newproject.NewProjectWizard; 32 33 import org.eclipse.core.resources.IFile; 34 import org.eclipse.jdt.core.IJavaProject; 35 import org.eclipse.jface.dialogs.Dialog; 36 import org.eclipse.jface.dialogs.IMessageProvider; 37 import org.eclipse.jface.viewers.ILabelProvider; 38 import org.eclipse.jface.viewers.ILabelProviderListener; 39 import org.eclipse.jface.window.Window; 40 import org.eclipse.swt.SWT; 41 import org.eclipse.swt.events.DisposeEvent; 42 import org.eclipse.swt.events.DisposeListener; 43 import org.eclipse.swt.events.ModifyEvent; 44 import org.eclipse.swt.events.ModifyListener; 45 import org.eclipse.swt.events.SelectionAdapter; 46 import org.eclipse.swt.events.SelectionEvent; 47 import org.eclipse.swt.graphics.Image; 48 import org.eclipse.swt.layout.GridData; 49 import org.eclipse.swt.layout.GridLayout; 50 import org.eclipse.swt.widgets.Button; 51 import org.eclipse.swt.widgets.Composite; 52 import org.eclipse.swt.widgets.Text; 53 import org.eclipse.ui.IWorkbenchPage; 54 import org.eclipse.ui.IWorkbenchWindow; 55 import org.eclipse.ui.PartInitException; 56 import org.eclipse.ui.PlatformUI; 57 import org.eclipse.ui.dialogs.ElementListSelectionDialog; 58 import org.eclipse.ui.forms.IManagedForm; 59 import org.eclipse.ui.forms.events.HyperlinkAdapter; 60 import org.eclipse.ui.forms.events.HyperlinkEvent; 61 import org.eclipse.ui.forms.widgets.FormText; 62 import org.eclipse.ui.forms.widgets.FormToolkit; 63 import org.eclipse.ui.forms.widgets.TableWrapData; 64 import org.eclipse.ui.part.FileEditorInput; 65 66 import java.util.TreeSet; 67 68 /** 69 * Represents an XML attribute to select an existing manifest package, that can be modified using 70 * a simple text field or a dialog to choose an existing package. 71 * <p/> 72 * See {@link UiTextAttributeNode} for more information. 73 */ 74 public class UiManifestPkgAttrNode extends UiTextAttributeNode { 75 76 /** 77 * Creates a {@link UiManifestPkgAttrNode} object that will display ui to select or create 78 * a manifest package. 79 * @param attributeDescriptor the {@link AttributeDescriptor} object linked to the Ui Node. 80 */ 81 public UiManifestPkgAttrNode(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 179 // Display the list of AndroidManifest packages in a selection dialog 180 ElementListSelectionDialog dialog = new ElementListSelectionDialog( 181 getTextWidget().getShell(), 182 new ILabelProvider() { 183 @Override 184 public Image getImage(Object element) { 185 return null; 186 } 187 188 @Override 189 public String getText(Object element) { 190 return element.toString(); 191 } 192 193 @Override 194 public void addListener(ILabelProviderListener listener) { 195 } 196 197 @Override 198 public void dispose() { 199 } 200 201 @Override 202 public boolean isLabelProperty(Object element, String property) { 203 return false; 204 } 205 206 @Override 207 public void removeListener(ILabelProviderListener listener) { 208 } 209 }); 210 211 dialog.setTitle("Android Manifest Package Selection"); 212 dialog.setMessage("Select the Android Manifest package to target."); 213 214 dialog.setElements(getPossibleValues(null)); 215 216 // open the dialog and use the object selected if OK was clicked, or null otherwise 217 if (dialog.open() == Window.OK) { 218 String result = (String) dialog.getFirstResult(); 219 if (result != null && result.length() > 0) { 220 getTextWidget().setText(result); 221 } 222 } 223 } 224 225 /** 226 * Handles response to the Label hyper link being activated. 227 */ 228 private void doLabelClick() { 229 // get the current package name 230 String package_name = getTextWidget().getText().trim(); 231 232 if (package_name.length() == 0) { 233 createNewProject(); 234 } else { 235 displayExistingManifest(package_name); 236 } 237 } 238 239 /** 240 * When the label is clicked and there's already a package name, this method 241 * attempts to find the project matching the android package name and it attempts 242 * to open the manifest editor. 243 * 244 * @param package_name The android package name to find. Must not be null. 245 */ 246 private void displayExistingManifest(String package_name) { 247 248 // Look for the first project that uses this package name 249 for (IJavaProject project : BaseProjectHelper.getAndroidProjects(null /*filter*/)) { 250 // check that there is indeed a manifest file. 251 IFile manifestFile = ProjectHelper.getManifest(project.getProject()); 252 if (manifestFile == null) { 253 // no file? skip this project. 254 continue; 255 } 256 257 ManifestData manifestData = AndroidManifestHelper.parseForData(manifestFile); 258 if (manifestData == null) { 259 // skip this project. 260 continue; 261 } 262 263 if (package_name.equals(manifestData.getPackage())) { 264 // Found the project. 265 266 IWorkbenchWindow win = PlatformUI.getWorkbench().getActiveWorkbenchWindow(); 267 if (win != null) { 268 IWorkbenchPage page = win.getActivePage(); 269 if (page != null) { 270 try { 271 page.openEditor( 272 new FileEditorInput(manifestFile), 273 ManifestEditor.ID, 274 true, /* activate */ 275 IWorkbenchPage.MATCH_INPUT); 276 } catch (PartInitException e) { 277 AdtPlugin.log(e, 278 "Opening editor failed for %s", //$NON-NLS-1$ 279 manifestFile.getFullPath()); 280 } 281 } 282 } 283 284 // We found the project; even if we failed there's no need to keep looking. 285 return; 286 } 287 } 288 } 289 290 /** 291 * Displays the New Project Wizard to create a new project. 292 * If one is successfully created, use the Android Package name. 293 */ 294 private void createNewProject() { 295 296 NewProjectAction npwAction = new NewProjectAction(); 297 npwAction.run(null /*action*/); 298 if (npwAction.getDialogResult() == Dialog.OK) { 299 NewProjectWizard npw = (NewProjectWizard) npwAction.getWizard(); 300 String name = npw.getPackageName(); 301 if (name != null && name.length() > 0) { 302 getTextWidget().setText(name); 303 } 304 } 305 } 306 307 /** 308 * Returns all the possible android package names that could be used. 309 * The prefix is not used. 310 * 311 * {@inheritDoc} 312 */ 313 @Override 314 public String[] getPossibleValues(String prefix) { 315 TreeSet<String> packages = new TreeSet<String>(); 316 317 for (IJavaProject project : BaseProjectHelper.getAndroidProjects(null /*filter*/)) { 318 // check that there is indeed a manifest file. 319 ManifestData manifestData = AndroidManifestHelper.parseForData(project.getProject()); 320 if (manifestData == null) { 321 // skip this project. 322 continue; 323 } 324 325 packages.add(manifestData.getPackage()); 326 } 327 328 return packages.toArray(new String[packages.size()]); 329 } 330 } 331 332