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.ui; 18 19 20 import com.android.ide.common.resources.ResourceItem; 21 import com.android.ide.common.resources.ResourceRepository; 22 import com.android.ide.eclipse.adt.AdtPlugin; 23 import com.android.ide.eclipse.adt.AdtUtils; 24 import com.android.ide.eclipse.adt.internal.assetstudio.OpenCreateAssetSetWizardAction; 25 import com.android.ide.eclipse.adt.internal.refactorings.extractstring.ExtractStringRefactoring; 26 import com.android.ide.eclipse.adt.internal.refactorings.extractstring.ExtractStringWizard; 27 import com.android.ide.eclipse.adt.internal.resources.ResourceHelper; 28 import com.android.ide.eclipse.adt.internal.resources.ResourceNameValidator; 29 import com.android.resources.ResourceType; 30 import com.android.util.Pair; 31 32 import org.eclipse.core.resources.IFile; 33 import org.eclipse.core.resources.IProject; 34 import org.eclipse.core.resources.IResource; 35 import org.eclipse.core.runtime.IStatus; 36 import org.eclipse.core.runtime.Status; 37 import org.eclipse.jface.dialogs.IDialogConstants; 38 import org.eclipse.jface.dialogs.IInputValidator; 39 import org.eclipse.jface.dialogs.InputDialog; 40 import org.eclipse.jface.text.IRegion; 41 import org.eclipse.jface.window.Window; 42 import org.eclipse.ltk.ui.refactoring.RefactoringWizard; 43 import org.eclipse.ltk.ui.refactoring.RefactoringWizardOpenOperation; 44 import org.eclipse.swt.SWT; 45 import org.eclipse.swt.events.SelectionAdapter; 46 import org.eclipse.swt.events.SelectionEvent; 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.Composite; 51 import org.eclipse.swt.widgets.Control; 52 import org.eclipse.swt.widgets.Event; 53 import org.eclipse.swt.widgets.Label; 54 import org.eclipse.swt.widgets.Listener; 55 import org.eclipse.swt.widgets.Shell; 56 import org.eclipse.ui.IWorkbench; 57 import org.eclipse.ui.PlatformUI; 58 import org.eclipse.ui.dialogs.AbstractElementListSelectionDialog; 59 import org.eclipse.ui.dialogs.SelectionStatusDialog; 60 61 import java.util.Arrays; 62 import java.util.Collection; 63 import java.util.Collections; 64 import java.util.List; 65 import java.util.regex.Matcher; 66 import java.util.regex.Pattern; 67 68 /** 69 * A dialog to let the user select a resource based on a resource type. 70 */ 71 public class ResourceChooser extends AbstractElementListSelectionDialog { 72 /** The return code from the dialog for the user choosing "Clear" */ 73 public static final int CLEAR_RETURN_CODE = -5; 74 /** The dialog button ID for the user choosing "Clear" */ 75 private static final int CLEAR_BUTTON_ID = CLEAR_RETURN_CODE; 76 77 private Pattern mProjectResourcePattern; 78 private ResourceType mResourceType; 79 private final ResourceRepository mProjectResources; 80 private final ResourceRepository mFrameworkResources; 81 private Pattern mSystemResourcePattern; 82 private Button mProjectButton; 83 private Button mSystemButton; 84 private Button mNewButton; 85 private String mCurrentResource; 86 private final IProject mProject; 87 private IInputValidator mInputValidator; 88 private ResourcePreviewHelper mPreviewHelper; 89 90 /** 91 * Creates a Resource Chooser dialog. 92 * @param project Project being worked on 93 * @param type The type of the resource to choose 94 * @param projectResources The repository for the project 95 * @param frameworkResources The Framework resource repository 96 * @param parent the parent shell 97 */ 98 public ResourceChooser(IProject project, ResourceType type, 99 ResourceRepository projectResources, 100 ResourceRepository frameworkResources, 101 Shell parent) { 102 super(parent, new ResourceLabelProvider()); 103 mProject = project; 104 105 mResourceType = type; 106 mProjectResources = projectResources; 107 mFrameworkResources = frameworkResources; 108 109 mProjectResourcePattern = Pattern.compile( 110 "@" + mResourceType.getName() + "/(.+)"); //$NON-NLS-1$ //$NON-NLS-2$ 111 112 mSystemResourcePattern = Pattern.compile( 113 "@android:" + mResourceType.getName() + "/(.+)"); //$NON-NLS-1$ //$NON-NLS-2$ 114 115 setTitle("Resource Chooser"); 116 setMessage(String.format("Choose a %1$s resource", 117 mResourceType.getDisplayName().toLowerCase())); 118 } 119 120 public void setPreviewHelper(ResourcePreviewHelper previewHelper) { 121 mPreviewHelper = previewHelper; 122 } 123 124 @Override 125 protected void createButtonsForButtonBar(Composite parent) { 126 createButton(parent, CLEAR_BUTTON_ID, "Clear", false /*defaultButton*/); 127 super.createButtonsForButtonBar(parent); 128 } 129 130 @Override 131 protected void buttonPressed(int buttonId) { 132 super.buttonPressed(buttonId); 133 134 if (buttonId == CLEAR_BUTTON_ID) { 135 assert CLEAR_RETURN_CODE != Window.OK && CLEAR_RETURN_CODE != Window.CANCEL; 136 setReturnCode(CLEAR_RETURN_CODE); 137 close(); 138 } 139 } 140 141 public void setCurrentResource(String resource) { 142 mCurrentResource = resource; 143 } 144 145 public String getCurrentResource() { 146 return mCurrentResource; 147 } 148 149 public void setInputValidator(IInputValidator inputValidator) { 150 mInputValidator = inputValidator; 151 } 152 153 @Override 154 protected void computeResult() { 155 if (getSelectionIndex() == -1) { 156 mCurrentResource = null; 157 return; 158 } 159 160 Object[] elements = getSelectedElements(); 161 if (elements.length == 1 && elements[0] instanceof ResourceItem) { 162 ResourceItem item = (ResourceItem)elements[0]; 163 164 mCurrentResource = item.getXmlString(mResourceType, mSystemButton.getSelection()); 165 166 if (mInputValidator != null && mInputValidator.isValid(mCurrentResource) != null) { 167 mCurrentResource = null; 168 } 169 } 170 } 171 172 @Override 173 protected Control createDialogArea(Composite parent) { 174 Composite top = (Composite)super.createDialogArea(parent); 175 176 createMessageArea(top); 177 178 createButtons(top); 179 createFilterText(top); 180 createFilteredList(top); 181 182 // create the "New Resource" button 183 createNewResButtons(top); 184 185 setupResourceList(); 186 selectResourceString(mCurrentResource); 187 188 return top; 189 } 190 191 /** 192 * Creates the radio button to switch between project and system resources. 193 * @param top the parent composite 194 */ 195 private void createButtons(Composite top) { 196 mProjectButton = new Button(top, SWT.RADIO); 197 mProjectButton.setText("Project Resources"); 198 mProjectButton.addSelectionListener(new SelectionAdapter() { 199 @Override 200 public void widgetSelected(SelectionEvent e) { 201 super.widgetSelected(e); 202 if (mProjectButton.getSelection()) { 203 setupResourceList(); 204 updateNewButton(false /*isSystem*/); 205 updatePreview(); 206 } 207 } 208 }); 209 mSystemButton = new Button(top, SWT.RADIO); 210 mSystemButton.setText("System Resources"); 211 mSystemButton.addSelectionListener(new SelectionAdapter() { 212 @Override 213 public void widgetSelected(SelectionEvent e) { 214 super.widgetSelected(e); 215 if (mSystemButton.getSelection()) { 216 setupResourceList(); 217 updateNewButton(true /*isSystem*/); 218 updatePreview(); 219 } 220 } 221 }); 222 } 223 224 /** 225 * Creates the "New Resource" button. 226 * @param top the parent composite 227 */ 228 private void createNewResButtons(Composite top) { 229 mNewButton = new Button(top, SWT.NONE); 230 231 String title = String.format("New %1$s...", mResourceType.getDisplayName()); 232 if (mResourceType == ResourceType.DRAWABLE) { 233 title = "Create New Icon..."; 234 } 235 mNewButton.setText(title); 236 237 mNewButton.addSelectionListener(new SelectionAdapter() { 238 @Override 239 public void widgetSelected(SelectionEvent e) { 240 super.widgetSelected(e); 241 242 if (mResourceType == ResourceType.STRING) { 243 // Special case: Use Extract String refactoring wizard UI 244 String newName = createNewString(); 245 selectAddedItem(newName); 246 } else if (mResourceType == ResourceType.DRAWABLE) { 247 // Special case: Use the "Create Icon Set" wizard 248 OpenCreateAssetSetWizardAction action = 249 new OpenCreateAssetSetWizardAction(mProject); 250 action.run(); 251 List<IResource> files = action.getCreatedFiles(); 252 if (files != null && files.size() > 0) { 253 String newName = AdtUtils.stripAllExtensions(files.get(0).getName()); 254 // Recompute the "current resource" to select the new id 255 ResourceItem[] items = setupResourceList(); 256 selectItemName(newName, items); 257 } 258 } else { 259 if (ResourceHelper.isValueBasedResourceType(mResourceType)) { 260 String newName = createNewValue(mResourceType); 261 selectAddedItem(newName); 262 } else { 263 String newName = createNewFile(mResourceType); 264 selectAddedItem(newName); 265 } 266 } 267 } 268 269 private void selectAddedItem(String newName) { 270 // Recompute the "current resource" to select the new id 271 ResourceItem[] items = setupResourceList(); 272 273 // Ensure that the name is in the list. There's a delay after 274 // an item is added (until the builder runs and processes the delta) 275 // so if it's not in the list, add it 276 boolean found = false; 277 for (ResourceItem item : items) { 278 if (newName.equals(item.getName())) { 279 found = true; 280 break; 281 } 282 } 283 if (!found) { 284 ResourceItem[] newItems = new ResourceItem[items.length + 1]; 285 System.arraycopy(items, 0, newItems, 0, items.length); 286 newItems[items.length] = new ResourceItem(newName); 287 items = newItems; 288 Arrays.sort(items); 289 setListElements(items); 290 } 291 292 selectItemName(newName, items); 293 } 294 }); 295 } 296 297 @Override 298 protected void handleSelectionChanged() { 299 super.handleSelectionChanged(); 300 if (mInputValidator != null) { 301 Object[] elements = getSelectedElements(); 302 if (elements.length == 1 && elements[0] instanceof ResourceItem) { 303 ResourceItem item = (ResourceItem)elements[0]; 304 String current = item.getXmlString(mResourceType, mSystemButton.getSelection()); 305 String error = mInputValidator.isValid(current); 306 IStatus status; 307 if (error != null) { 308 status = new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, error); 309 } else { 310 status = new Status(IStatus.OK, AdtPlugin.PLUGIN_ID, null); 311 } 312 updateStatus(status); 313 } 314 } 315 316 updatePreview(); 317 } 318 319 private void updatePreview() { 320 if (mPreviewHelper != null) { 321 computeResult(); 322 mPreviewHelper.updatePreview(mResourceType, mCurrentResource); 323 } 324 } 325 326 private String createNewFile(ResourceType type) { 327 // Show a name/value dialog entering the key name and the value 328 Shell shell = AdtPlugin.getDisplay().getActiveShell(); 329 if (shell == null) { 330 return null; 331 } 332 333 ResourceNameValidator validator = ResourceNameValidator.create(true /*allowXmlExtension*/, 334 mProject, mResourceType); 335 InputDialog d = new InputDialog( 336 AdtPlugin.getDisplay().getActiveShell(), 337 "Enter name", // title 338 "Enter name", 339 "", //$NON-NLS-1$ 340 validator); 341 if (d.open() == Window.OK) { 342 String name = d.getValue().trim(); 343 if (name.length() == 0) { 344 return null; 345 } 346 347 Pair<IFile, IRegion> resource = ResourceHelper.createResource(mProject, type, name, 348 null); 349 if (resource != null) { 350 return name; 351 } 352 } 353 354 return null; 355 } 356 357 358 private String createNewValue(ResourceType type) { 359 // Show a name/value dialog entering the key name and the value 360 Shell shell = AdtPlugin.getDisplay().getActiveShell(); 361 if (shell == null) { 362 return null; 363 } 364 NameValueDialog dialog = new NameValueDialog(shell, getFilter()); 365 if (dialog.open() != Window.OK) { 366 return null; 367 } 368 369 String name = dialog.getName(); 370 String value = dialog.getValue(); 371 if (name.length() == 0 || value.length() == 0) { 372 return null; 373 } 374 375 Pair<IFile, IRegion> resource = ResourceHelper.createResource(mProject, type, name, value); 376 if (resource != null) { 377 return name; 378 } 379 380 return null; 381 } 382 383 private String createNewString() { 384 ExtractStringRefactoring ref = new ExtractStringRefactoring( 385 mProject, true /*enforceNew*/); 386 RefactoringWizard wizard = new ExtractStringWizard(ref, mProject); 387 RefactoringWizardOpenOperation op = new RefactoringWizardOpenOperation(wizard); 388 try { 389 IWorkbench w = PlatformUI.getWorkbench(); 390 if (op.run(w.getDisplay().getActiveShell(), wizard.getDefaultPageTitle()) == 391 IDialogConstants.OK_ID) { 392 return ref.getXmlStringId(); 393 } 394 } catch (InterruptedException ex) { 395 // Interrupted. Pass. 396 } 397 398 return null; 399 } 400 401 /** 402 * Setups the current list. 403 */ 404 private ResourceItem[] setupResourceList() { 405 Collection<ResourceItem> items = null; 406 if (mProjectButton.getSelection()) { 407 items = mProjectResources.getResourceItemsOfType(mResourceType); 408 } else if (mSystemButton.getSelection()) { 409 items = mFrameworkResources.getResourceItemsOfType(mResourceType); 410 } 411 412 if (items == null) { 413 items = Collections.emptyList(); 414 } 415 416 ResourceItem[] arrayItems = items.toArray(new ResourceItem[items.size()]); 417 418 // sort the array 419 Arrays.sort(arrayItems); 420 421 setListElements(arrayItems); 422 423 return arrayItems; 424 } 425 426 /** 427 * Select an item by its name, if possible. 428 */ 429 private void selectItemName(String itemName, ResourceItem[] items) { 430 if (itemName == null || items == null) { 431 return; 432 } 433 434 for (ResourceItem item : items) { 435 if (itemName.equals(item.getName())) { 436 setSelection(new Object[] { item }); 437 break; 438 } 439 } 440 } 441 442 /** 443 * Select an item by its full resource string. 444 * This also selects between project and system repository based on the resource string. 445 */ 446 private void selectResourceString(String resourceString) { 447 boolean isSystem = false; 448 String itemName = null; 449 450 if (resourceString != null) { 451 // Is this a system resource? 452 // If not a system resource or if they are not available, this will be a project res. 453 Matcher m = mSystemResourcePattern.matcher(resourceString); 454 if (m.matches()) { 455 itemName = m.group(1); 456 isSystem = true; 457 } 458 459 if (!isSystem && itemName == null) { 460 // Try to match project resource name 461 m = mProjectResourcePattern.matcher(resourceString); 462 if (m.matches()) { 463 itemName = m.group(1); 464 } 465 } 466 } 467 468 // Update the repository selection 469 mProjectButton.setSelection(!isSystem); 470 mSystemButton.setSelection(isSystem); 471 updateNewButton(isSystem); 472 473 // Update the list 474 ResourceItem[] items = setupResourceList(); 475 476 // If we have a selection name, select it 477 if (itemName != null) { 478 selectItemName(itemName, items); 479 } 480 } 481 482 private void updateNewButton(boolean isSystem) { 483 mNewButton.setEnabled(!isSystem && ResourceHelper.canCreateResourceType(mResourceType)); 484 } 485 486 /** Dialog asking for a Name/Value pair */ 487 private class NameValueDialog extends SelectionStatusDialog implements Listener { 488 private org.eclipse.swt.widgets.Text mNameText; 489 private org.eclipse.swt.widgets.Text mValueText; 490 private String mInitialName; 491 private String mName; 492 private String mValue; 493 private ResourceNameValidator mValidator; 494 495 public NameValueDialog(Shell parent, String initialName) { 496 super(parent); 497 mInitialName = initialName; 498 } 499 500 @Override 501 protected Control createDialogArea(Composite parent) { 502 Composite container = new Composite(parent, SWT.NONE); 503 container.setLayout(new GridLayout(2, false)); 504 GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1); 505 // Wide enough to accommodate the error label 506 gridData.widthHint = 500; 507 container.setLayoutData(gridData); 508 509 510 Label nameLabel = new Label(container, SWT.NONE); 511 nameLabel.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1)); 512 nameLabel.setText("Name:"); 513 514 mNameText = new org.eclipse.swt.widgets.Text(container, SWT.BORDER); 515 mNameText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1)); 516 if (mInitialName != null) { 517 mNameText.setText(mInitialName); 518 mNameText.selectAll(); 519 } 520 521 Label valueLabel = new Label(container, SWT.NONE); 522 valueLabel.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1)); 523 valueLabel.setText("Value:"); 524 525 mValueText = new org.eclipse.swt.widgets.Text(container, SWT.BORDER); 526 mValueText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1)); 527 528 mNameText.addListener(SWT.Modify, this); 529 mValueText.addListener(SWT.Modify, this); 530 531 validate(); 532 533 return container; 534 } 535 536 @Override 537 protected void computeResult() { 538 mName = mNameText.getText().trim(); 539 mValue = mValueText.getText().trim(); 540 } 541 542 private String getName() { 543 return mName; 544 } 545 546 private String getValue() { 547 return mValue; 548 } 549 550 public void handleEvent(Event event) { 551 validate(); 552 } 553 554 private void validate() { 555 IStatus status; 556 computeResult(); 557 if (mName.length() == 0) { 558 status = new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, "Enter a name"); 559 } else if (mValue.length() == 0) { 560 status = new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, "Enter a value"); 561 } else { 562 if (mValidator == null) { 563 mValidator = ResourceNameValidator.create(false, mProject, mResourceType); 564 } 565 String error = mValidator.isValid(mName); 566 if (error != null) { 567 status = new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, error); 568 } else { 569 status = new Status(IStatus.OK, AdtPlugin.PLUGIN_ID, null); 570 } 571 } 572 updateStatus(status); 573 } 574 } 575 } 576