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