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.launch; 18 19 import com.android.ide.eclipse.adt.internal.editors.IconFactory; 20 import com.android.ide.eclipse.adt.internal.project.AndroidManifestHelper; 21 import com.android.ide.eclipse.adt.internal.project.ProjectChooserHelper; 22 import com.android.ide.eclipse.adt.internal.project.ProjectChooserHelper.NonLibraryProjectOnlyFilter; 23 import com.android.sdklib.xml.ManifestData; 24 import com.android.sdklib.xml.ManifestData.Activity; 25 26 import org.eclipse.core.resources.IProject; 27 import org.eclipse.core.resources.IResource; 28 import org.eclipse.core.resources.IWorkspaceRoot; 29 import org.eclipse.core.resources.ResourcesPlugin; 30 import org.eclipse.core.runtime.CoreException; 31 import org.eclipse.debug.core.ILaunchConfiguration; 32 import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy; 33 import org.eclipse.debug.ui.AbstractLaunchConfigurationTab; 34 import org.eclipse.debug.ui.ILaunchConfigurationTab; 35 import org.eclipse.jdt.core.IJavaModel; 36 import org.eclipse.jdt.core.IJavaProject; 37 import org.eclipse.jdt.core.JavaCore; 38 import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants; 39 import org.eclipse.swt.SWT; 40 import org.eclipse.swt.events.ModifyEvent; 41 import org.eclipse.swt.events.ModifyListener; 42 import org.eclipse.swt.events.SelectionAdapter; 43 import org.eclipse.swt.events.SelectionEvent; 44 import org.eclipse.swt.events.SelectionListener; 45 import org.eclipse.swt.graphics.Font; 46 import org.eclipse.swt.graphics.Image; 47 import org.eclipse.swt.layout.GridData; 48 import org.eclipse.swt.layout.GridLayout; 49 import org.eclipse.swt.widgets.Button; 50 import org.eclipse.swt.widgets.Combo; 51 import org.eclipse.swt.widgets.Composite; 52 import org.eclipse.swt.widgets.Group; 53 import org.eclipse.swt.widgets.Text; 54 55 import java.util.ArrayList; 56 57 /** 58 * Class for the main launch configuration tab. 59 */ 60 public class MainLaunchConfigTab extends AbstractLaunchConfigurationTab { 61 62 /** 63 * 64 */ 65 public static final String LAUNCH_TAB_IMAGE = "mainLaunchTab"; //$NON-NLS-1$ 66 67 protected static final String EMPTY_STRING = ""; //$NON-NLS-1$ 68 69 protected Text mProjText; 70 private Button mProjButton; 71 72 private Combo mActivityCombo; 73 private final ArrayList<Activity> mActivities = new ArrayList<Activity>(); 74 75 private WidgetListener mListener = new WidgetListener(); 76 77 private Button mDefaultActionButton; 78 private Button mActivityActionButton; 79 private Button mDoNothingActionButton; 80 private int mLaunchAction = LaunchConfigDelegate.DEFAULT_LAUNCH_ACTION; 81 82 private ProjectChooserHelper mProjectChooserHelper; 83 84 /** 85 * A listener which handles widget change events for the controls in this 86 * tab. 87 */ 88 private class WidgetListener implements ModifyListener, SelectionListener { 89 90 public void modifyText(ModifyEvent e) { 91 IProject project = checkParameters(); 92 loadActivities(project); 93 setDirty(true); 94 } 95 96 public void widgetDefaultSelected(SelectionEvent e) {/* do nothing */ 97 } 98 99 public void widgetSelected(SelectionEvent e) { 100 Object source = e.getSource(); 101 if (source == mProjButton) { 102 handleProjectButtonSelected(); 103 } else { 104 checkParameters(); 105 } 106 } 107 } 108 109 public MainLaunchConfigTab() { 110 } 111 112 public void createControl(Composite parent) { 113 mProjectChooserHelper = new ProjectChooserHelper(parent.getShell(), 114 new NonLibraryProjectOnlyFilter()); 115 116 Font font = parent.getFont(); 117 Composite comp = new Composite(parent, SWT.NONE); 118 setControl(comp); 119 GridLayout topLayout = new GridLayout(); 120 topLayout.verticalSpacing = 0; 121 comp.setLayout(topLayout); 122 comp.setFont(font); 123 createProjectEditor(comp); 124 createVerticalSpacer(comp, 1); 125 126 // create the combo for the activity chooser 127 Group group = new Group(comp, SWT.NONE); 128 group.setText("Launch Action:"); 129 GridData gd = new GridData(GridData.FILL_HORIZONTAL); 130 group.setLayoutData(gd); 131 GridLayout layout = new GridLayout(); 132 layout.numColumns = 2; 133 group.setLayout(layout); 134 group.setFont(font); 135 136 mDefaultActionButton = new Button(group, SWT.RADIO); 137 gd = new GridData(GridData.FILL_HORIZONTAL); 138 gd.horizontalSpan = 2; 139 mDefaultActionButton.setLayoutData(gd); 140 mDefaultActionButton.setText("Launch Default Activity"); 141 mDefaultActionButton.addSelectionListener(new SelectionAdapter() { 142 @Override 143 public void widgetSelected(SelectionEvent e) { 144 // event are received for both selection and deselection, so we only process 145 // the selection event to avoid doing it twice. 146 if (mDefaultActionButton.getSelection() == true) { 147 mLaunchAction = LaunchConfigDelegate.ACTION_DEFAULT; 148 mActivityCombo.setEnabled(false); 149 checkParameters(); 150 } 151 } 152 }); 153 154 mActivityActionButton = new Button(group, SWT.RADIO); 155 mActivityActionButton.setText("Launch:"); 156 mActivityActionButton.addSelectionListener(new SelectionAdapter() { 157 @Override 158 public void widgetSelected(SelectionEvent e) { 159 // event are received for both selection and deselection, so we only process 160 // the selection event to avoid doing it twice. 161 if (mActivityActionButton.getSelection() == true) { 162 mLaunchAction = LaunchConfigDelegate.ACTION_ACTIVITY; 163 mActivityCombo.setEnabled(true); 164 checkParameters(); 165 } 166 } 167 }); 168 169 mActivityCombo = new Combo(group, SWT.DROP_DOWN | SWT.READ_ONLY); 170 gd = new GridData(GridData.FILL_HORIZONTAL); 171 mActivityCombo.setLayoutData(gd); 172 mActivityCombo.clearSelection(); 173 mActivityCombo.setEnabled(false); 174 mActivityCombo.addSelectionListener(new SelectionAdapter() { 175 @Override 176 public void widgetSelected(SelectionEvent e) { 177 checkParameters(); 178 } 179 }); 180 181 mDoNothingActionButton = new Button(group, SWT.RADIO); 182 gd = new GridData(GridData.FILL_HORIZONTAL); 183 gd.horizontalSpan = 2; 184 mDoNothingActionButton.setLayoutData(gd); 185 mDoNothingActionButton.setText("Do Nothing"); 186 mDoNothingActionButton.addSelectionListener(new SelectionAdapter() { 187 @Override 188 public void widgetSelected(SelectionEvent e) { 189 // event are received for both selection and deselection, so we only process 190 // the selection event to avoid doing it twice. 191 if (mDoNothingActionButton.getSelection() == true) { 192 mLaunchAction = LaunchConfigDelegate.ACTION_DO_NOTHING; 193 mActivityCombo.setEnabled(false); 194 checkParameters(); 195 } 196 } 197 }); 198 199 } 200 201 public String getName() { 202 return "Android"; 203 } 204 205 @Override 206 public Image getImage() { 207 return IconFactory.getInstance().getIcon(LAUNCH_TAB_IMAGE); 208 } 209 210 public void performApply(ILaunchConfigurationWorkingCopy configuration) { 211 configuration.setAttribute( 212 IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME, mProjText.getText()); 213 configuration.setAttribute( 214 IJavaLaunchConfigurationConstants.ATTR_ALLOW_TERMINATE, true); 215 216 // add the launch mode 217 configuration.setAttribute(LaunchConfigDelegate.ATTR_LAUNCH_ACTION, mLaunchAction); 218 219 // add the activity 220 int selection = mActivityCombo.getSelectionIndex(); 221 if (mActivities != null && selection >=0 && selection < mActivities.size()) { 222 configuration.setAttribute(LaunchConfigDelegate.ATTR_ACTIVITY, 223 mActivities.get(selection).getName()); 224 } 225 226 // link the project and the launch config. 227 mapResources(configuration); 228 } 229 230 public void setDefaults(ILaunchConfigurationWorkingCopy configuration) { 231 configuration.setAttribute(LaunchConfigDelegate.ATTR_LAUNCH_ACTION, 232 LaunchConfigDelegate.DEFAULT_LAUNCH_ACTION); 233 } 234 235 /** 236 * Creates the widgets for specifying a main type. 237 * 238 * @param parent the parent composite 239 */ 240 protected void createProjectEditor(Composite parent) { 241 Font font = parent.getFont(); 242 Group group = new Group(parent, SWT.NONE); 243 group.setText("Project:"); 244 GridData gd = new GridData(GridData.FILL_HORIZONTAL); 245 group.setLayoutData(gd); 246 GridLayout layout = new GridLayout(); 247 layout.numColumns = 2; 248 group.setLayout(layout); 249 group.setFont(font); 250 mProjText = new Text(group, SWT.SINGLE | SWT.BORDER); 251 gd = new GridData(GridData.FILL_HORIZONTAL); 252 mProjText.setLayoutData(gd); 253 mProjText.setFont(font); 254 mProjText.addModifyListener(mListener); 255 mProjButton = createPushButton(group, "Browse...", null); 256 mProjButton.addSelectionListener(mListener); 257 } 258 259 /** 260 * returns the default listener from this class. For all subclasses this 261 * listener will only provide the functi Jaonality of updating the current 262 * tab 263 * 264 * @return a widget listener 265 */ 266 protected WidgetListener getDefaultListener() { 267 return mListener; 268 } 269 270 /** 271 * Return the {@link IJavaProject} corresponding to the project name in the project 272 * name text field, or null if the text does not match a project name. 273 * @param javaModel the Java Model object corresponding for the current workspace root. 274 * @return a IJavaProject object or null. 275 */ 276 protected IJavaProject getJavaProject(IJavaModel javaModel) { 277 String projectName = mProjText.getText().trim(); 278 if (projectName.length() < 1) { 279 return null; 280 } 281 return javaModel.getJavaProject(projectName); 282 } 283 284 /** 285 * Show a dialog that lets the user select a project. This in turn provides 286 * context for the main type, allowing the user to key a main type name, or 287 * constraining the search for main types to the specified project. 288 */ 289 protected void handleProjectButtonSelected() { 290 IJavaProject javaProject = mProjectChooserHelper.chooseJavaProject( 291 mProjText.getText().trim(), 292 "Please select a project to launch"); 293 if (javaProject == null) { 294 return; 295 }// end if 296 String projectName = javaProject.getElementName(); 297 mProjText.setText(projectName); 298 299 // get the list of activities and fill the combo 300 IProject project = javaProject.getProject(); 301 loadActivities(project); 302 }// end handle selected 303 304 /** 305 * Initializes this tab's controls with values from the given 306 * launch configuration. This method is called when 307 * a configuration is selected to view or edit, after this 308 * tab's control has been created. 309 * 310 * @param config launch configuration 311 * 312 * @see ILaunchConfigurationTab 313 */ 314 public void initializeFrom(ILaunchConfiguration config) { 315 String projectName = EMPTY_STRING; 316 try { 317 projectName = config.getAttribute(IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME, 318 EMPTY_STRING); 319 }// end try 320 catch (CoreException ce) { 321 } 322 mProjText.setText(projectName); 323 324 IProject proj = mProjectChooserHelper.getAndroidProject(projectName); 325 loadActivities(proj); 326 327 // load the launch action. 328 mLaunchAction = LaunchConfigDelegate.DEFAULT_LAUNCH_ACTION; 329 try { 330 mLaunchAction = config.getAttribute(LaunchConfigDelegate.ATTR_LAUNCH_ACTION, 331 mLaunchAction); 332 } catch (CoreException e) { 333 // nothing to be done really. launchAction will keep its default value. 334 } 335 336 mDefaultActionButton.setSelection(mLaunchAction == LaunchConfigDelegate.ACTION_DEFAULT); 337 mActivityActionButton.setSelection(mLaunchAction == LaunchConfigDelegate.ACTION_ACTIVITY); 338 mDoNothingActionButton.setSelection( 339 mLaunchAction == LaunchConfigDelegate.ACTION_DO_NOTHING); 340 341 // now look for the activity and load it if present, otherwise, revert 342 // to the current one. 343 String activityName = EMPTY_STRING; 344 try { 345 activityName = config.getAttribute(LaunchConfigDelegate.ATTR_ACTIVITY, EMPTY_STRING); 346 }// end try 347 catch (CoreException ce) { 348 // nothing to be done really. activityName will stay empty 349 } 350 351 if (mLaunchAction != LaunchConfigDelegate.ACTION_ACTIVITY) { 352 mActivityCombo.setEnabled(false); 353 mActivityCombo.clearSelection(); 354 } else { 355 mActivityCombo.setEnabled(true); 356 if (activityName == null || activityName.equals(EMPTY_STRING)) { 357 mActivityCombo.clearSelection(); 358 } else if (mActivities != null && mActivities.size() > 0) { 359 // look for the name of the activity in the combo. 360 boolean found = false; 361 for (int i = 0 ; i < mActivities.size() ; i++) { 362 if (activityName.equals(mActivities.get(i).getName())) { 363 found = true; 364 mActivityCombo.select(i); 365 break; 366 } 367 } 368 369 // if we haven't found a matching activity we clear the combo selection 370 if (found == false) { 371 mActivityCombo.clearSelection(); 372 } 373 } 374 } 375 } 376 377 /** 378 * Associates the launch config and the project. This allows Eclipse to delete the launch 379 * config when the project is deleted. 380 * 381 * @param config the launch config working copy. 382 */ 383 protected void mapResources(ILaunchConfigurationWorkingCopy config) { 384 // get the java model 385 IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot(); 386 IJavaModel javaModel = JavaCore.create(workspaceRoot); 387 388 // get the IJavaProject described by the text field. 389 IJavaProject javaProject = getJavaProject(javaModel); 390 IResource[] resources = null; 391 if (javaProject != null) { 392 resources = AndroidLaunchController.getResourcesToMap(javaProject.getProject()); 393 } 394 config.setMappedResources(resources); 395 } 396 397 /** 398 * Loads the ui with the activities of the specified project, and stores the 399 * activities in <code>mActivities</code>. 400 * <p/> 401 * First activity is selected by default if present. 402 * 403 * @param project The project to load the activities from. 404 */ 405 private void loadActivities(IProject project) { 406 if (project != null) { 407 // parse the manifest for the list of activities. 408 ManifestData manifestData = AndroidManifestHelper.parseForData(project); 409 if (manifestData != null) { 410 Activity[] activities = manifestData.getActivities(); 411 412 mActivities.clear(); 413 mActivityCombo.removeAll(); 414 415 for (Activity activity : activities) { 416 if (activity.isExported() && activity.hasAction()) { 417 mActivities.add(activity); 418 mActivityCombo.add(activity.getName()); 419 } 420 } 421 422 if (mActivities.size() > 0) { 423 if (mLaunchAction == LaunchConfigDelegate.ACTION_ACTIVITY) { 424 mActivityCombo.setEnabled(true); 425 } 426 } else { 427 mActivityCombo.setEnabled(false); 428 } 429 430 // the selection will be set when we update the ui from the current 431 // config object. 432 mActivityCombo.clearSelection(); 433 434 return; 435 } 436 } 437 438 // if we reach this point, either project is null, or we got an exception during 439 // the parsing. In either case, we empty the activity list. 440 mActivityCombo.removeAll(); 441 mActivities.clear(); 442 } 443 444 /** 445 * Checks the parameters for correctness, and update the error message and buttons. 446 * @return the current IProject of this launch config. 447 */ 448 private IProject checkParameters() { 449 try { 450 //test the project name first! 451 String text = mProjText.getText(); 452 if (text.length() == 0) { 453 setErrorMessage("Project Name is required!"); 454 } else if (text.matches("[a-zA-Z0-9_ \\.-]+") == false) { 455 setErrorMessage("Project name contains unsupported characters!"); 456 } else { 457 IJavaProject[] projects = mProjectChooserHelper.getAndroidProjects(null); 458 IProject found = null; 459 for (IJavaProject javaProject : projects) { 460 if (javaProject.getProject().getName().equals(text)) { 461 found = javaProject.getProject(); 462 break; 463 } 464 465 } 466 467 if (found != null) { 468 setErrorMessage(null); 469 } else { 470 setErrorMessage(String.format("There is no android project named '%1$s'", 471 text)); 472 } 473 474 return found; 475 } 476 } finally { 477 updateLaunchConfigurationDialog(); 478 } 479 480 return null; 481 } 482 } 483