1 /* 2 * Copyright (C) 2012 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.wizards.newproject; 17 18 import com.android.ide.eclipse.adt.AdtPlugin; 19 import com.android.tools.lint.detector.api.LintUtils; 20 21 import org.eclipse.core.resources.IProject; 22 import org.eclipse.core.resources.IWorkspaceRoot; 23 import org.eclipse.core.resources.ResourcesPlugin; 24 import org.eclipse.core.runtime.IStatus; 25 import org.eclipse.core.runtime.Status; 26 import org.eclipse.jface.dialogs.IMessageProvider; 27 import org.eclipse.jface.viewers.CellEditor; 28 import org.eclipse.jface.viewers.CellLabelProvider; 29 import org.eclipse.jface.viewers.CheckStateChangedEvent; 30 import org.eclipse.jface.viewers.CheckboxTableViewer; 31 import org.eclipse.jface.viewers.ColumnViewer; 32 import org.eclipse.jface.viewers.EditingSupport; 33 import org.eclipse.jface.viewers.ICheckStateListener; 34 import org.eclipse.jface.viewers.IStructuredContentProvider; 35 import org.eclipse.jface.viewers.IStructuredSelection; 36 import org.eclipse.jface.viewers.TableViewerColumn; 37 import org.eclipse.jface.viewers.TextCellEditor; 38 import org.eclipse.jface.viewers.Viewer; 39 import org.eclipse.jface.viewers.ViewerCell; 40 import org.eclipse.jface.wizard.IWizardPage; 41 import org.eclipse.jface.wizard.WizardPage; 42 import org.eclipse.swt.SWT; 43 import org.eclipse.swt.events.ControlEvent; 44 import org.eclipse.swt.events.ControlListener; 45 import org.eclipse.swt.events.KeyEvent; 46 import org.eclipse.swt.events.KeyListener; 47 import org.eclipse.swt.events.SelectionEvent; 48 import org.eclipse.swt.events.SelectionListener; 49 import org.eclipse.swt.events.TraverseEvent; 50 import org.eclipse.swt.events.TraverseListener; 51 import org.eclipse.swt.graphics.Color; 52 import org.eclipse.swt.graphics.Rectangle; 53 import org.eclipse.swt.layout.GridData; 54 import org.eclipse.swt.layout.GridLayout; 55 import org.eclipse.swt.widgets.Button; 56 import org.eclipse.swt.widgets.Composite; 57 import org.eclipse.swt.widgets.DirectoryDialog; 58 import org.eclipse.swt.widgets.Display; 59 import org.eclipse.swt.widgets.Label; 60 import org.eclipse.swt.widgets.Table; 61 import org.eclipse.swt.widgets.TableColumn; 62 import org.eclipse.swt.widgets.Text; 63 import org.eclipse.ui.IWorkbenchPart; 64 import org.eclipse.ui.IWorkingSet; 65 66 import java.io.File; 67 import java.util.ArrayList; 68 import java.util.Collections; 69 import java.util.List; 70 71 /** WizardPage for importing Android projects */ 72 class ImportPage extends WizardPage implements SelectionListener, IStructuredContentProvider, 73 ICheckStateListener, KeyListener, TraverseListener, ControlListener { 74 private static final int DIR_COLUMN = 0; 75 private static final int NAME_COLUMN = 1; 76 77 private final NewProjectWizardState mValues; 78 private List<ImportedProject> mProjectPaths; 79 private final IProject[] mExistingProjects; 80 81 private Text mDir; 82 private Button mBrowseButton; 83 private Button mCopyCheckBox; 84 private Button mRefreshButton; 85 private Button mDeselectAllButton; 86 private Button mSelectAllButton; 87 private Table mTable; 88 private CheckboxTableViewer mCheckboxTableViewer; 89 private WorkingSetGroup mWorkingSetGroup; 90 91 ImportPage(NewProjectWizardState values) { 92 super("importPage"); //$NON-NLS-1$ 93 mValues = values; 94 setTitle("Import Projects"); 95 setDescription("Select a directory to search for existing Android projects"); 96 mWorkingSetGroup = new WorkingSetGroup(); 97 setWorkingSets(new IWorkingSet[0]); 98 99 // Record all projects such that we can ensure that the project names are unique 100 IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot(); 101 mExistingProjects = workspaceRoot.getProjects(); 102 } 103 104 public void init(IStructuredSelection selection, IWorkbenchPart activePart) { 105 setWorkingSets(WorkingSetHelper.getSelectedWorkingSet(selection, activePart)); 106 } 107 108 @SuppressWarnings("unused") // SWT constructors have side effects and aren't unused 109 @Override 110 public void createControl(Composite parent) { 111 Composite container = new Composite(parent, SWT.NULL); 112 setControl(container); 113 container.setLayout(new GridLayout(3, false)); 114 115 Label directoryLabel = new Label(container, SWT.NONE); 116 directoryLabel.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1)); 117 directoryLabel.setText("Root Directory:"); 118 119 mDir = new Text(container, SWT.BORDER); 120 mDir.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1)); 121 mDir.addKeyListener(this); 122 mDir.addTraverseListener(this); 123 124 mBrowseButton = new Button(container, SWT.NONE); 125 mBrowseButton.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 1, 1)); 126 mBrowseButton.setText("Browse..."); 127 mBrowseButton.addSelectionListener(this); 128 129 Label projectsLabel = new Label(container, SWT.NONE); 130 projectsLabel.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 3, 1)); 131 projectsLabel.setText("Projects:"); 132 133 mTable = new Table(container, SWT.CHECK); 134 mTable.setHeaderVisible(true); 135 mCheckboxTableViewer = new CheckboxTableViewer(mTable); 136 137 TableViewerColumn dirViewerColumn = new TableViewerColumn(mCheckboxTableViewer, SWT.NONE); 138 TableColumn dirColumn = dirViewerColumn.getColumn(); 139 dirColumn.setWidth(200); 140 dirColumn.setText("Project to Import"); 141 TableViewerColumn nameViewerColumn = new TableViewerColumn(mCheckboxTableViewer, SWT.NONE); 142 TableColumn nameColumn = nameViewerColumn.getColumn(); 143 nameColumn.setWidth(200); 144 nameColumn.setText("New Project Name"); 145 nameViewerColumn.setEditingSupport(new ProjectNameEditingSupport(mCheckboxTableViewer)); 146 147 mTable.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 2, 4)); 148 mTable.setLinesVisible(true); 149 mTable.setHeaderVisible(true); 150 mTable.addSelectionListener(this); 151 mTable.addControlListener(this); 152 mCheckboxTableViewer.setContentProvider(this); 153 mCheckboxTableViewer.setInput(this); 154 mCheckboxTableViewer.addCheckStateListener(this); 155 mCheckboxTableViewer.setLabelProvider(new ProjectCellLabelProvider()); 156 157 mSelectAllButton = new Button(container, SWT.NONE); 158 mSelectAllButton.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 1, 1)); 159 mSelectAllButton.setText("Select All"); 160 mSelectAllButton.addSelectionListener(this); 161 162 mDeselectAllButton = new Button(container, SWT.NONE); 163 mDeselectAllButton.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 1, 1)); 164 mDeselectAllButton.setText("Deselect All"); 165 mDeselectAllButton.addSelectionListener(this); 166 167 mRefreshButton = new Button(container, SWT.NONE); 168 mRefreshButton.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 1, 1)); 169 mRefreshButton.setText("Refresh"); 170 mRefreshButton.addSelectionListener(this); 171 new Label(container, SWT.NONE); 172 173 mCopyCheckBox = new Button(container, SWT.CHECK); 174 mCopyCheckBox.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 3, 1)); 175 mCopyCheckBox.setText("Copy projects into workspace"); 176 mCopyCheckBox.addSelectionListener(this); 177 178 Composite group = mWorkingSetGroup.createControl(container); 179 group.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 3, 1)); 180 181 updateColumnWidths(); 182 } 183 184 private void updateColumnWidths() { 185 Rectangle r = mTable.getClientArea(); 186 int availableWidth = r.width; 187 // Add all available size to the first column 188 for (int i = 1; i < mTable.getColumnCount(); i++) { 189 TableColumn column = mTable.getColumn(i); 190 availableWidth -= column.getWidth(); 191 } 192 if (availableWidth > 100) { 193 mTable.getColumn(0).setWidth(availableWidth); 194 } 195 } 196 197 @Override 198 public void setVisible(boolean visible) { 199 super.setVisible(visible); 200 validatePage(); 201 } 202 203 private void refresh() { 204 File root = new File(mDir.getText().trim()); 205 mProjectPaths = searchForProjects(root); 206 mCheckboxTableViewer.refresh(); 207 mCheckboxTableViewer.setAllChecked(true); 208 209 updateValidity(); 210 validatePage(); 211 } 212 213 private void updateValidity(){ 214 List<ImportedProject> selected = new ArrayList<ImportedProject>(); 215 List<ImportedProject> disabled = new ArrayList<ImportedProject>(); 216 for (ImportedProject project : mProjectPaths) { 217 String projectName = project.getProjectName(); 218 boolean invalid = false; 219 for (IProject existingProject : mExistingProjects) { 220 if (projectName.equals(existingProject.getName())) { 221 invalid = true; 222 break; 223 } 224 } 225 if (invalid) { 226 disabled.add(project); 227 } else { 228 selected.add(project); 229 } 230 } 231 232 mValues.importProjects = selected; 233 234 mCheckboxTableViewer.setGrayedElements(disabled.toArray()); 235 mCheckboxTableViewer.setCheckedElements(selected.toArray()); 236 mCheckboxTableViewer.refresh(); 237 mCheckboxTableViewer.getTable().setFocus(); 238 } 239 240 private List<ImportedProject> searchForProjects(File dir) { 241 List<ImportedProject> projects = new ArrayList<ImportedProject>(); 242 addProjects(dir, projects, dir.getPath().length() + 1); 243 return projects; 244 } 245 246 /** Finds all project directories under the given directory */ 247 private void addProjects(File dir, List<ImportedProject> projects, int prefixLength) { 248 if (dir.isDirectory()) { 249 if (LintUtils.isManifestFolder(dir)) { 250 String relative = dir.getPath(); 251 if (relative.length() > prefixLength) { 252 relative = relative.substring(prefixLength); 253 } 254 projects.add(new ImportedProject(dir, relative)); 255 } 256 257 File[] children = dir.listFiles(); 258 if (children != null) { 259 for (File child : children) { 260 addProjects(child, projects, prefixLength); 261 } 262 } 263 } 264 } 265 266 private void validatePage() { 267 IStatus status = null; 268 269 // Validate project name -- unless we're creating a sample, in which case 270 // the user will get a chance to pick the name on the Sample page 271 if (mProjectPaths == null || mProjectPaths.isEmpty()) { 272 status = new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, 273 "Select a directory to search for existing Android projects"); 274 } else if (mValues.importProjects == null || mValues.importProjects.isEmpty()) { 275 status = new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, 276 "Select at least one project"); 277 } else { 278 for (ImportedProject project : mValues.importProjects) { 279 if (mCheckboxTableViewer.getGrayed(project)) { 280 status = new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, 281 String.format("Cannot import %1$s because the project name is in use", 282 project.getProjectName())); 283 break; 284 } else { 285 status = ProjectNamePage.validateProjectName(project.getProjectName()); 286 if (status != null && !status.isOK()) { 287 // Need to insert project name to make it clear which project name 288 // is in violation 289 if (mValues.importProjects.size() > 1) { 290 String message = String.format("%1$s: %2$s", 291 project.getProjectName(), status.getMessage()); 292 status = new Status(status.getSeverity(), AdtPlugin.PLUGIN_ID, 293 message); 294 } 295 break; 296 } else { 297 status = null; // Don't leave non null status with isOK() == true 298 } 299 } 300 } 301 } 302 303 // -- update UI & enable finish if there's no error 304 setPageComplete(status == null || status.getSeverity() != IStatus.ERROR); 305 if (status != null) { 306 setMessage(status.getMessage(), 307 status.getSeverity() == IStatus.ERROR 308 ? IMessageProvider.ERROR : IMessageProvider.WARNING); 309 } else { 310 setErrorMessage(null); 311 setMessage(null); 312 } 313 } 314 315 /** 316 * Returns the working sets to which the new project should be added. 317 * 318 * @return the selected working sets to which the new project should be added 319 */ 320 private IWorkingSet[] getWorkingSets() { 321 return mWorkingSetGroup.getSelectedWorkingSets(); 322 } 323 324 /** 325 * Sets the working sets to which the new project should be added. 326 * 327 * @param workingSets the initial selected working sets 328 */ 329 private void setWorkingSets(IWorkingSet[] workingSets) { 330 assert workingSets != null; 331 mWorkingSetGroup.setWorkingSets(workingSets); 332 } 333 334 @Override 335 public IWizardPage getNextPage() { 336 // Sync working set data to the value object, since the WorkingSetGroup 337 // doesn't let us add listeners to do this lazily 338 mValues.workingSets = getWorkingSets(); 339 340 return super.getNextPage(); 341 } 342 343 // ---- Implements SelectionListener ---- 344 345 @Override 346 public void widgetSelected(SelectionEvent e) { 347 Object source = e.getSource(); 348 if (source == mBrowseButton) { 349 // Choose directory 350 DirectoryDialog dialog = new DirectoryDialog(getShell(), SWT.OPEN); 351 String path = mDir.getText().trim(); 352 if (path.length() > 0) { 353 dialog.setFilterPath(path); 354 } 355 String file = dialog.open(); 356 if (file != null) { 357 mDir.setText(file); 358 refresh(); 359 } 360 } else if (source == mSelectAllButton) { 361 mCheckboxTableViewer.setAllChecked(true); 362 mValues.importProjects = mProjectPaths; 363 } else if (source == mDeselectAllButton) { 364 mCheckboxTableViewer.setAllChecked(false); 365 mValues.importProjects = Collections.emptyList(); 366 } else if (source == mRefreshButton || source == mDir) { 367 refresh(); 368 } else if (source == mCopyCheckBox) { 369 mValues.copyIntoWorkspace = mCopyCheckBox.getSelection(); 370 } 371 372 validatePage(); 373 } 374 375 @Override 376 public void widgetDefaultSelected(SelectionEvent e) { 377 } 378 379 // ---- KeyListener ---- 380 381 @Override 382 public void keyPressed(KeyEvent e) { 383 if (e.getSource() == mDir) { 384 if (e.keyCode == SWT.CR) { 385 refresh(); 386 } 387 } 388 } 389 390 @Override 391 public void keyReleased(KeyEvent e) { 392 } 393 394 // ---- TraverseListener ---- 395 396 @Override 397 public void keyTraversed(TraverseEvent e) { 398 // Prevent Return from running through the wizard; return is handled by 399 // key listener to refresh project list instead 400 if (SWT.TRAVERSE_RETURN == e.detail) { 401 e.doit = false; 402 } 403 } 404 405 // ---- Implements IStructuredContentProvider ---- 406 407 @Override 408 public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { 409 } 410 411 @Override 412 public Object[] getElements(Object inputElement) { 413 return mProjectPaths != null ? mProjectPaths.toArray() : new Object[0]; 414 } 415 416 // ---- Implements ICheckStateListener ---- 417 418 @Override 419 public void checkStateChanged(CheckStateChangedEvent event) { 420 // Try to disable other elements that conflict with this 421 Object[] checked = mCheckboxTableViewer.getCheckedElements(); 422 List<ImportedProject> selected = new ArrayList<ImportedProject>(checked.length); 423 for (Object o : checked) { 424 if (!mCheckboxTableViewer.getGrayed(o)) { 425 selected.add((ImportedProject) o); 426 } 427 } 428 mValues.importProjects = selected; 429 validatePage(); 430 431 mCheckboxTableViewer.update(event.getElement(), null); 432 } 433 434 // ---- Implements ControlListener ---- 435 436 @Override 437 public void controlMoved(ControlEvent e) { 438 } 439 440 @Override 441 public void controlResized(ControlEvent e) { 442 updateColumnWidths(); 443 } 444 445 private final class ProjectCellLabelProvider extends CellLabelProvider { 446 @Override 447 public void update(ViewerCell cell) { 448 Object element = cell.getElement(); 449 int index = cell.getColumnIndex(); 450 ImportedProject project = (ImportedProject) element; 451 452 Display display = mTable.getDisplay(); 453 Color fg; 454 if (mCheckboxTableViewer.getGrayed(element)) { 455 fg = display.getSystemColor(SWT.COLOR_DARK_GRAY); 456 } else { 457 fg = display.getSystemColor(SWT.COLOR_LIST_FOREGROUND); 458 } 459 cell.setForeground(fg); 460 cell.setBackground(display.getSystemColor(SWT.COLOR_LIST_BACKGROUND)); 461 462 switch (index) { 463 case DIR_COLUMN: { 464 // Directory name 465 cell.setText(project.getRelativePath()); 466 return; 467 } 468 469 case NAME_COLUMN: { 470 // New name 471 cell.setText(project.getProjectName()); 472 return; 473 } 474 default: 475 assert false : index; 476 } 477 cell.setText(""); 478 } 479 } 480 481 /** Editing support for the project name column */ 482 private class ProjectNameEditingSupport extends EditingSupport { 483 private ProjectNameEditingSupport(ColumnViewer viewer) { 484 super(viewer); 485 } 486 487 @Override 488 protected void setValue(Object element, Object value) { 489 ImportedProject project = (ImportedProject) element; 490 project.setProjectName(value.toString()); 491 mCheckboxTableViewer.update(element, null); 492 updateValidity(); 493 validatePage(); 494 } 495 496 @Override 497 protected Object getValue(Object element) { 498 ImportedProject project = (ImportedProject) element; 499 return project.getProjectName(); 500 } 501 502 @Override 503 protected CellEditor getCellEditor(Object element) { 504 return new TextCellEditor(mTable); 505 } 506 507 @Override 508 protected boolean canEdit(Object element) { 509 return true; 510 } 511 } 512 } 513