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 import static com.android.SdkConstants.ANDROID_PREFIX; 20 import static com.android.SdkConstants.PREFIX_RESOURCE_REF; 21 22 import com.android.annotations.NonNull; 23 import com.android.annotations.Nullable; 24 import com.android.ide.common.rendering.api.ResourceValue; 25 import com.android.ide.common.resources.ResourceItem; 26 import com.android.ide.common.resources.ResourceRepository; 27 import com.android.ide.common.resources.ResourceResolver; 28 import com.android.ide.eclipse.adt.AdtPlugin; 29 import com.android.ide.eclipse.adt.AdtUtils; 30 import com.android.ide.eclipse.adt.internal.assetstudio.OpenCreateAssetSetWizardAction; 31 import com.android.ide.eclipse.adt.internal.editors.layout.gle2.GraphicalEditorPart; 32 import com.android.ide.eclipse.adt.internal.editors.layout.properties.PropertyFactory; 33 import com.android.ide.eclipse.adt.internal.refactorings.extractstring.ExtractStringRefactoring; 34 import com.android.ide.eclipse.adt.internal.refactorings.extractstring.ExtractStringWizard; 35 import com.android.ide.eclipse.adt.internal.resources.ResourceHelper; 36 import com.android.ide.eclipse.adt.internal.resources.ResourceNameValidator; 37 import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResources; 38 import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager; 39 import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData; 40 import com.android.ide.eclipse.adt.internal.sdk.ProjectState; 41 import com.android.ide.eclipse.adt.internal.sdk.Sdk; 42 import com.android.resources.ResourceType; 43 import com.android.utils.Pair; 44 import com.google.common.collect.Maps; 45 46 import org.eclipse.core.resources.IFile; 47 import org.eclipse.core.resources.IProject; 48 import org.eclipse.core.resources.IResource; 49 import org.eclipse.core.runtime.IStatus; 50 import org.eclipse.core.runtime.Status; 51 import org.eclipse.jface.dialogs.IDialogConstants; 52 import org.eclipse.jface.dialogs.IInputValidator; 53 import org.eclipse.jface.dialogs.InputDialog; 54 import org.eclipse.jface.text.IRegion; 55 import org.eclipse.jface.window.Window; 56 import org.eclipse.ltk.ui.refactoring.RefactoringWizard; 57 import org.eclipse.ltk.ui.refactoring.RefactoringWizardOpenOperation; 58 import org.eclipse.swt.SWT; 59 import org.eclipse.swt.events.ModifyEvent; 60 import org.eclipse.swt.events.ModifyListener; 61 import org.eclipse.swt.events.SelectionAdapter; 62 import org.eclipse.swt.events.SelectionEvent; 63 import org.eclipse.swt.layout.GridData; 64 import org.eclipse.swt.layout.GridLayout; 65 import org.eclipse.swt.widgets.Button; 66 import org.eclipse.swt.widgets.Composite; 67 import org.eclipse.swt.widgets.Control; 68 import org.eclipse.swt.widgets.Event; 69 import org.eclipse.swt.widgets.Label; 70 import org.eclipse.swt.widgets.Listener; 71 import org.eclipse.swt.widgets.Shell; 72 import org.eclipse.swt.widgets.Text; 73 import org.eclipse.ui.IWorkbench; 74 import org.eclipse.ui.PlatformUI; 75 import org.eclipse.ui.dialogs.AbstractElementListSelectionDialog; 76 import org.eclipse.ui.dialogs.SelectionStatusDialog; 77 78 import java.util.ArrayList; 79 import java.util.Arrays; 80 import java.util.Collection; 81 import java.util.Collections; 82 import java.util.List; 83 import java.util.Locale; 84 import java.util.Map; 85 import java.util.regex.Matcher; 86 import java.util.regex.Pattern; 87 88 /** 89 * A dialog to let the user select a resource based on a resource type. 90 */ 91 public class ResourceChooser extends AbstractElementListSelectionDialog implements ModifyListener { 92 /** The return code from the dialog for the user choosing "Clear" */ 93 public static final int CLEAR_RETURN_CODE = -5; 94 /** The dialog button ID for the user choosing "Clear" */ 95 private static final int CLEAR_BUTTON_ID = CLEAR_RETURN_CODE; 96 97 private Pattern mProjectResourcePattern; 98 private ResourceType mResourceType; 99 private final List<ResourceRepository> mProjectResources; 100 private final ResourceRepository mFrameworkResources; 101 private Pattern mSystemResourcePattern; 102 private Button mProjectButton; 103 private Button mSystemButton; 104 private Button mNewButton; 105 private String mCurrentResource; 106 private final IProject mProject; 107 private IInputValidator mInputValidator; 108 109 /** Helper object used to draw previews for drawables and colors. */ 110 private ResourcePreviewHelper mPreviewHelper; 111 112 /** 113 * Textfield for editing the actual returned value, updated when selection 114 * changes. Only shown if {@link #mShowValueText} is true. 115 */ 116 private Text mEditValueText; 117 118 /** 119 * Whether the {@link #mEditValueText} textfield should be shown when the dialog is created. 120 */ 121 private boolean mShowValueText; 122 123 /** 124 * Flag indicating whether it's the first time {@link #handleSelectionChanged()} is called. 125 * This is used to filter out the first selection event, always called by the superclass 126 * when the widget is created, to distinguish between "the dialog was created" and 127 * "the user clicked on a selection result", since only the latter should wipe out the 128 * manual user edit shown in the value text. 129 */ 130 private boolean mFirstSelect = true; 131 132 /** 133 * Label used to show the resolved value in the resource chooser. Only shown 134 * if the {@link #mResourceResolver} field is set. 135 */ 136 private Label mResolvedLabel; 137 138 /** Resource resolver used to show actual values for resources selected. (Optional). */ 139 private ResourceResolver mResourceResolver; 140 141 /** 142 * Creates a Resource Chooser dialog. 143 * @param project Project being worked on 144 * @param type The type of the resource to choose 145 * @param projectResources The repository for the project 146 * @param frameworkResources The Framework resource repository 147 * @param parent the parent shell 148 */ ResourceChooser( @onNull IProject project, @NonNull ResourceType type, @NonNull List<ResourceRepository> projectResources, @Nullable ResourceRepository frameworkResources, @NonNull Shell parent)149 private ResourceChooser( 150 @NonNull IProject project, 151 @NonNull ResourceType type, 152 @NonNull List<ResourceRepository> projectResources, 153 @Nullable ResourceRepository frameworkResources, 154 @NonNull Shell parent) { 155 super(parent, new ResourceLabelProvider()); 156 mProject = project; 157 158 mResourceType = type; 159 mProjectResources = projectResources; 160 mFrameworkResources = frameworkResources; 161 162 mProjectResourcePattern = Pattern.compile( 163 PREFIX_RESOURCE_REF + mResourceType.getName() + "/(.+)"); //$NON-NLS-1$ 164 165 mSystemResourcePattern = Pattern.compile( 166 ANDROID_PREFIX + mResourceType.getName() + "/(.+)"); //$NON-NLS-1$ 167 168 setTitle("Resource Chooser"); 169 setMessage(String.format("Choose a %1$s resource", 170 mResourceType.getDisplayName().toLowerCase(Locale.US))); 171 } 172 173 /** 174 * Creates a new {@link ResourceChooser} 175 * 176 * @param editor the associated layout editor 177 * @param type the resource type to choose 178 * @return a new {@link ResourceChooser} 179 */ 180 @NonNull create( @onNull GraphicalEditorPart editor, @NonNull ResourceType type)181 public static ResourceChooser create( 182 @NonNull GraphicalEditorPart editor, 183 @NonNull ResourceType type) { 184 IProject project = editor.getProject(); 185 Shell parent = editor.getCanvasControl().getShell(); 186 AndroidTargetData targetData = editor.getEditorDelegate().getEditor().getTargetData(); 187 ResourceChooser chooser = create(project, type, targetData, parent); 188 189 // When editing Strings, allow editing the value text directly. When we 190 // get inline editing support (where values entered directly into the 191 // textual widget are translated automatically into a resource) this can 192 // go away. 193 if (type == ResourceType.STRING) { 194 chooser.setResourceResolver(editor.getResourceResolver()); 195 chooser.setShowValueText(true); 196 } else if (type == ResourceType.DIMEN || type == ResourceType.INTEGER) { 197 chooser.setResourceResolver(editor.getResourceResolver()); 198 } 199 200 chooser.setPreviewHelper(new ResourcePreviewHelper(chooser, editor)); 201 return chooser; 202 } 203 204 /** 205 * Creates a new {@link ResourceChooser} 206 * 207 * @param project the associated project 208 * @param type the resource type to choose 209 * @param targetData the associated framework target data 210 * @param parent the target shell 211 * @return a new {@link ResourceChooser} 212 */ 213 @NonNull create( @onNull IProject project, @NonNull ResourceType type, @Nullable AndroidTargetData targetData, @NonNull Shell parent)214 public static ResourceChooser create( 215 @NonNull IProject project, 216 @NonNull ResourceType type, 217 @Nullable AndroidTargetData targetData, 218 @NonNull Shell parent) { 219 ResourceManager manager = ResourceManager.getInstance(); 220 221 List<ResourceRepository> projectResources = new ArrayList<ResourceRepository>(); 222 ProjectResources resources = manager.getProjectResources(project); 223 projectResources.add(resources); 224 225 // Add in library project resources 226 ProjectState projectState = Sdk.getProjectState(project); 227 if (projectState != null) { 228 List<IProject> libraries = projectState.getFullLibraryProjects(); 229 if (libraries != null && !libraries.isEmpty()) { 230 for (IProject library : libraries) { 231 projectResources.add(manager.getProjectResources(library)); 232 } 233 } 234 } 235 236 ResourceRepository frameworkResources = 237 targetData != null ? targetData.getFrameworkResources() : null; 238 return new ResourceChooser(project, type, projectResources, frameworkResources, parent); 239 } 240 241 /** 242 * Sets whether this dialog should show the value field as a separate text 243 * value (and take the resulting value of the dialog from this text field 244 * rather than from the selection) 245 * 246 * @param showValueText if true, show the value text field 247 * @return this, for constructor chaining 248 */ setShowValueText(boolean showValueText)249 public ResourceChooser setShowValueText(boolean showValueText) { 250 mShowValueText = showValueText; 251 252 return this; 253 } 254 255 /** 256 * Sets the resource resolver to use to show resolved values for the current 257 * selection 258 * 259 * @param resourceResolver the resource resolver to use 260 * @return this, for constructor chaining 261 */ setResourceResolver(ResourceResolver resourceResolver)262 public ResourceChooser setResourceResolver(ResourceResolver resourceResolver) { 263 mResourceResolver = resourceResolver; 264 265 return this; 266 } 267 268 /** 269 * Sets the {@link ResourcePreviewHelper} to use to preview drawable 270 * resources, if any 271 * 272 * @param previewHelper the helper to use 273 * @return this, for constructor chaining 274 */ setPreviewHelper(ResourcePreviewHelper previewHelper)275 public ResourceChooser setPreviewHelper(ResourcePreviewHelper previewHelper) { 276 mPreviewHelper = previewHelper; 277 278 return this; 279 } 280 281 /** 282 * Sets the initial dialog size 283 * 284 * @param width the initial width 285 * @param height the initial height 286 * @return this, for constructor chaining 287 */ setInitialSize(int width, int height)288 public ResourceChooser setInitialSize(int width, int height) { 289 setSize(width, height); 290 291 return this; 292 } 293 294 @Override create()295 public void create() { 296 super.create(); 297 298 if (mShowValueText) { 299 mEditValueText.selectAll(); 300 mEditValueText.setFocus(); 301 } 302 } 303 304 @Override createButtonsForButtonBar(Composite parent)305 protected void createButtonsForButtonBar(Composite parent) { 306 createButton(parent, CLEAR_BUTTON_ID, "Clear", false /*defaultButton*/); 307 super.createButtonsForButtonBar(parent); 308 } 309 310 @Override buttonPressed(int buttonId)311 protected void buttonPressed(int buttonId) { 312 super.buttonPressed(buttonId); 313 314 if (buttonId == CLEAR_BUTTON_ID) { 315 assert CLEAR_RETURN_CODE != Window.OK && CLEAR_RETURN_CODE != Window.CANCEL; 316 setReturnCode(CLEAR_RETURN_CODE); 317 close(); 318 } 319 } 320 321 /** 322 * Sets the currently selected item 323 * 324 * @param resource the resource url for the currently selected item 325 * @return this, for constructor chaining 326 */ setCurrentResource(@ullable String resource)327 public ResourceChooser setCurrentResource(@Nullable String resource) { 328 mCurrentResource = resource; 329 330 if (mShowValueText && mEditValueText != null) { 331 mEditValueText.setText(resource); 332 } 333 334 return this; 335 } 336 337 /** 338 * Returns the currently selected url 339 * 340 * @return the currently selected url 341 */ 342 @Nullable getCurrentResource()343 public String getCurrentResource() { 344 return mCurrentResource; 345 } 346 347 /** 348 * Sets the input validator to use, if any 349 * 350 * @param inputValidator the validator 351 * @return this, for constructor chaining 352 */ setInputValidator(@ullable IInputValidator inputValidator)353 public ResourceChooser setInputValidator(@Nullable IInputValidator inputValidator) { 354 mInputValidator = inputValidator; 355 356 return this; 357 } 358 359 @Override computeResult()360 protected void computeResult() { 361 if (mShowValueText) { 362 mCurrentResource = mEditValueText.getText(); 363 if (mCurrentResource.length() == 0) { 364 mCurrentResource = null; 365 } 366 return; 367 } 368 369 computeResultFromSelection(); 370 } 371 computeResultFromSelection()372 private void computeResultFromSelection() { 373 if (getSelectionIndex() == -1) { 374 mCurrentResource = null; 375 return; 376 } 377 378 Object[] elements = getSelectedElements(); 379 if (elements.length == 1 && elements[0] instanceof ResourceItem) { 380 ResourceItem item = (ResourceItem)elements[0]; 381 382 mCurrentResource = item.getXmlString(mResourceType, mSystemButton.getSelection()); 383 384 if (mInputValidator != null && mInputValidator.isValid(mCurrentResource) != null) { 385 mCurrentResource = null; 386 } 387 } 388 } 389 390 @Override createDialogArea(Composite parent)391 protected Control createDialogArea(Composite parent) { 392 Composite top = (Composite)super.createDialogArea(parent); 393 394 createMessageArea(top); 395 396 createButtons(top); 397 createFilterText(top); 398 createFilteredList(top); 399 400 // create the "New Resource" button 401 createNewResButtons(top); 402 403 // Optionally create the value text field, if {@link #mShowValueText} is true 404 createValueField(top); 405 406 setupResourceList(); 407 selectResourceString(mCurrentResource); 408 409 return top; 410 } 411 412 /** 413 * Creates the radio button to switch between project and system resources. 414 * @param top the parent composite 415 */ createButtons(Composite top)416 private void createButtons(Composite top) { 417 mProjectButton = new Button(top, SWT.RADIO); 418 mProjectButton.setText("Project Resources"); 419 mProjectButton.addSelectionListener(new SelectionAdapter() { 420 @Override 421 public void widgetSelected(SelectionEvent e) { 422 super.widgetSelected(e); 423 if (mProjectButton.getSelection()) { 424 // Clear selection before changing the list contents. This works around 425 // a bug in the superclass where switching to the framework resources, 426 // choosing one of the last resources, then switching to the project 427 // resources would cause an exception when calling getSelection() because 428 // selection state doesn't get cleared when we set new contents on 429 // the filtered list. 430 fFilteredList.setSelection(new int[0]); 431 setupResourceList(); 432 updateNewButton(false /*isSystem*/); 433 updateValue(); 434 } 435 } 436 }); 437 mSystemButton = new Button(top, SWT.RADIO); 438 mSystemButton.setText("System Resources"); 439 mSystemButton.addSelectionListener(new SelectionAdapter() { 440 @Override 441 public void widgetSelected(SelectionEvent e) { 442 super.widgetSelected(e); 443 if (mSystemButton.getSelection()) { 444 fFilteredList.setSelection(new int[0]); 445 setupResourceList(); 446 updateNewButton(true /*isSystem*/); 447 updateValue(); 448 } 449 } 450 }); 451 if (mFrameworkResources == null) { 452 mSystemButton.setVisible(false); 453 } 454 } 455 456 /** 457 * Creates the "New Resource" button. 458 * @param top the parent composite 459 */ createNewResButtons(Composite top)460 private void createNewResButtons(Composite top) { 461 mNewButton = new Button(top, SWT.NONE); 462 463 String title = String.format("New %1$s...", mResourceType.getDisplayName()); 464 if (mResourceType == ResourceType.DRAWABLE) { 465 title = "Create New Icon..."; 466 } 467 mNewButton.setText(title); 468 469 mNewButton.addSelectionListener(new SelectionAdapter() { 470 @Override 471 public void widgetSelected(SelectionEvent e) { 472 super.widgetSelected(e); 473 474 if (mResourceType == ResourceType.STRING) { 475 // Special case: Use Extract String refactoring wizard UI 476 String newName = createNewString(); 477 selectAddedItem(newName); 478 } else if (mResourceType == ResourceType.DRAWABLE) { 479 // Special case: Use the "Create Icon Set" wizard 480 OpenCreateAssetSetWizardAction action = 481 new OpenCreateAssetSetWizardAction(mProject); 482 action.run(); 483 List<IResource> files = action.getCreatedFiles(); 484 if (files != null && files.size() > 0) { 485 String newName = AdtUtils.stripAllExtensions(files.get(0).getName()); 486 // Recompute the "current resource" to select the new id 487 ResourceItem[] items = setupResourceList(); 488 selectItemName(newName, items); 489 } 490 } else { 491 if (ResourceHelper.isValueBasedResourceType(mResourceType)) { 492 String newName = createNewValue(mResourceType); 493 if (newName != null) { 494 selectAddedItem(newName); 495 } 496 } else { 497 String newName = createNewFile(mResourceType); 498 if (newName != null) { 499 selectAddedItem(newName); 500 } 501 } 502 } 503 } 504 505 private void selectAddedItem(@NonNull String newName) { 506 // Recompute the "current resource" to select the new id 507 ResourceItem[] items = setupResourceList(); 508 509 // Ensure that the name is in the list. There's a delay after 510 // an item is added (until the builder runs and processes the delta) 511 // so if it's not in the list, add it 512 boolean found = false; 513 for (ResourceItem item : items) { 514 if (newName.equals(item.getName())) { 515 found = true; 516 break; 517 } 518 } 519 if (!found) { 520 ResourceItem[] newItems = new ResourceItem[items.length + 1]; 521 System.arraycopy(items, 0, newItems, 0, items.length); 522 newItems[items.length] = new ResourceItem(newName); 523 items = newItems; 524 Arrays.sort(items); 525 setListElements(items); 526 fFilteredList.setEnabled(newItems.length > 0); 527 } 528 529 selectItemName(newName, items); 530 } 531 }); 532 } 533 534 /** 535 * Creates the value text field. 536 * 537 * @param top the parent composite 538 */ createValueField(Composite top)539 private void createValueField(Composite top) { 540 if (mShowValueText) { 541 mEditValueText = new Text(top, SWT.BORDER); 542 if (mCurrentResource != null) { 543 mEditValueText.setText(mCurrentResource); 544 } 545 mEditValueText.addModifyListener(this); 546 547 GridData data = new GridData(); 548 data.grabExcessVerticalSpace = false; 549 data.grabExcessHorizontalSpace = true; 550 data.horizontalAlignment = GridData.FILL; 551 data.verticalAlignment = GridData.BEGINNING; 552 mEditValueText.setLayoutData(data); 553 mEditValueText.setFont(top.getFont()); 554 } 555 556 if (mResourceResolver != null) { 557 mResolvedLabel = new Label(top, SWT.NONE); 558 GridData data = new GridData(); 559 data.grabExcessVerticalSpace = false; 560 data.grabExcessHorizontalSpace = true; 561 data.horizontalAlignment = GridData.FILL; 562 data.verticalAlignment = GridData.BEGINNING; 563 mResolvedLabel.setLayoutData(data); 564 } 565 566 Composite workaround = PropertyFactory.addWorkaround(top); 567 if (workaround != null) { 568 workaround.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false, 1, 1)); 569 } 570 } 571 updateResolvedLabel()572 private void updateResolvedLabel() { 573 if (mResourceResolver == null) { 574 return; 575 } 576 577 String v = null; 578 if (mCurrentResource != null) { 579 v = mCurrentResource; 580 if (mCurrentResource.startsWith(PREFIX_RESOURCE_REF)) { 581 ResourceValue value = mResourceResolver.findResValue(mCurrentResource, false); 582 if (value != null) { 583 v = value.getValue(); 584 } 585 } 586 } 587 588 if (v == null) { 589 v = ""; 590 } 591 592 mResolvedLabel.setText(String.format("Resolved Value: %1$s", v)); 593 } 594 595 @Override handleSelectionChanged()596 protected void handleSelectionChanged() { 597 super.handleSelectionChanged(); 598 if (mInputValidator != null) { 599 Object[] elements = getSelectedElements(); 600 if (elements.length == 1 && elements[0] instanceof ResourceItem) { 601 ResourceItem item = (ResourceItem)elements[0]; 602 String current = item.getXmlString(mResourceType, mSystemButton.getSelection()); 603 String error = mInputValidator.isValid(current); 604 IStatus status; 605 if (error != null) { 606 status = new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, error); 607 } else { 608 status = new Status(IStatus.OK, AdtPlugin.PLUGIN_ID, null); 609 } 610 updateStatus(status); 611 } 612 } 613 614 updateValue(); 615 } 616 updateValue()617 private void updateValue() { 618 if (mPreviewHelper != null) { 619 computeResult(); 620 mPreviewHelper.updatePreview(mResourceType, mCurrentResource); 621 } 622 623 if (mShowValueText) { 624 if (mFirstSelect) { 625 mFirstSelect = false; 626 mEditValueText.selectAll(); 627 } else { 628 computeResultFromSelection(); 629 mEditValueText.setText(mCurrentResource != null ? mCurrentResource : ""); 630 } 631 } 632 633 if (mResourceResolver != null) { 634 if (!mShowValueText) { 635 computeResultFromSelection(); 636 } 637 updateResolvedLabel(); 638 } 639 } 640 641 @Nullable createNewFile(ResourceType type)642 private String createNewFile(ResourceType type) { 643 // Show a name/value dialog entering the key name and the value 644 Shell shell = AdtPlugin.getShell(); 645 if (shell == null) { 646 return null; 647 } 648 649 ResourceNameValidator validator = ResourceNameValidator.create(true /*allowXmlExtension*/, 650 mProject, mResourceType); 651 InputDialog d = new InputDialog( 652 AdtPlugin.getShell(), 653 "Enter name", // title 654 "Enter name", 655 "", //$NON-NLS-1$ 656 validator); 657 if (d.open() == Window.OK) { 658 String name = d.getValue().trim(); 659 if (name.length() == 0) { 660 return null; 661 } 662 663 Pair<IFile, IRegion> resource = ResourceHelper.createResource(mProject, type, name, 664 null); 665 if (resource != null) { 666 return name; 667 } 668 } 669 670 return null; 671 } 672 673 674 @Nullable createNewValue(ResourceType type)675 private String createNewValue(ResourceType type) { 676 // Show a name/value dialog entering the key name and the value 677 Shell shell = AdtPlugin.getShell(); 678 if (shell == null) { 679 return null; 680 } 681 NameValueDialog dialog = new NameValueDialog(shell, getFilter()); 682 if (dialog.open() != Window.OK) { 683 return null; 684 } 685 686 String name = dialog.getName(); 687 String value = dialog.getValue(); 688 if (name.length() == 0 || value.length() == 0) { 689 return null; 690 } 691 692 Pair<IFile, IRegion> resource = ResourceHelper.createResource(mProject, type, name, value); 693 if (resource != null) { 694 return name; 695 } 696 697 return null; 698 } 699 createNewString()700 private String createNewString() { 701 ExtractStringRefactoring ref = new ExtractStringRefactoring( 702 mProject, true /*enforceNew*/); 703 RefactoringWizard wizard = new ExtractStringWizard(ref, mProject); 704 RefactoringWizardOpenOperation op = new RefactoringWizardOpenOperation(wizard); 705 try { 706 IWorkbench w = PlatformUI.getWorkbench(); 707 if (op.run(w.getDisplay().getActiveShell(), wizard.getDefaultPageTitle()) == 708 IDialogConstants.OK_ID) { 709 return ref.getXmlStringId(); 710 } 711 } catch (InterruptedException ex) { 712 // Interrupted. Pass. 713 } 714 715 return null; 716 } 717 718 /** 719 * Setups the current list. 720 */ setupResourceList()721 private ResourceItem[] setupResourceList() { 722 Collection<ResourceItem> items = null; 723 if (mProjectButton.getSelection()) { 724 if (mProjectResources.size() == 1) { 725 items = mProjectResources.get(0).getResourceItemsOfType(mResourceType); 726 } else { 727 Map<String, ResourceItem> merged = Maps.newHashMapWithExpectedSize(200); 728 for (ResourceRepository repository : mProjectResources) { 729 for (ResourceItem item : repository.getResourceItemsOfType(mResourceType)) { 730 if (!merged.containsKey(item.getName())) { 731 merged.put(item.getName(), item); 732 } 733 } 734 } 735 items = merged.values(); 736 } 737 } else if (mSystemButton.getSelection()) { 738 items = mFrameworkResources.getResourceItemsOfType(mResourceType); 739 } 740 741 if (items == null) { 742 items = Collections.emptyList(); 743 } 744 745 ResourceItem[] arrayItems = items.toArray(new ResourceItem[items.size()]); 746 747 // sort the array 748 Arrays.sort(arrayItems); 749 750 setListElements(arrayItems); 751 fFilteredList.setEnabled(arrayItems.length > 0); 752 753 return arrayItems; 754 } 755 756 /** 757 * Select an item by its name, if possible. 758 */ selectItemName(String itemName, ResourceItem[] items)759 private void selectItemName(String itemName, ResourceItem[] items) { 760 if (itemName == null || items == null) { 761 return; 762 } 763 764 for (ResourceItem item : items) { 765 if (itemName.equals(item.getName())) { 766 setSelection(new Object[] { item }); 767 break; 768 } 769 } 770 } 771 772 /** 773 * Select an item by its full resource string. 774 * This also selects between project and system repository based on the resource string. 775 */ selectResourceString(String resourceString)776 private void selectResourceString(String resourceString) { 777 boolean isSystem = false; 778 String itemName = null; 779 780 if (resourceString != null) { 781 // Is this a system resource? 782 // If not a system resource or if they are not available, this will be a project res. 783 Matcher m = mSystemResourcePattern.matcher(resourceString); 784 if (m.matches()) { 785 itemName = m.group(1); 786 isSystem = true; 787 } 788 789 if (!isSystem && itemName == null) { 790 // Try to match project resource name 791 m = mProjectResourcePattern.matcher(resourceString); 792 if (m.matches()) { 793 itemName = m.group(1); 794 } 795 } 796 } 797 798 // Update the repository selection 799 mProjectButton.setSelection(!isSystem); 800 mSystemButton.setSelection(isSystem); 801 updateNewButton(isSystem); 802 803 // Update the list 804 ResourceItem[] items = setupResourceList(); 805 806 // If we have a selection name, select it 807 if (itemName != null) { 808 selectItemName(itemName, items); 809 } 810 } 811 updateNewButton(boolean isSystem)812 private void updateNewButton(boolean isSystem) { 813 mNewButton.setEnabled(!isSystem && ResourceHelper.canCreateResourceType(mResourceType)); 814 } 815 816 // ---- Implements ModifyListener ---- 817 818 @Override modifyText(ModifyEvent e)819 public void modifyText(ModifyEvent e) { 820 if (e.getSource() == mEditValueText && mResourceResolver != null) { 821 mCurrentResource = mEditValueText.getText(); 822 823 if (mCurrentResource.startsWith(PREFIX_RESOURCE_REF)) { 824 if (mProjectResourcePattern.matcher(mCurrentResource).matches() || 825 mSystemResourcePattern.matcher(mCurrentResource).matches()) { 826 updateResolvedLabel(); 827 } 828 } else { 829 updateResolvedLabel(); 830 } 831 } 832 } 833 834 /** Dialog asking for a Name/Value pair */ 835 private class NameValueDialog extends SelectionStatusDialog implements Listener { 836 private org.eclipse.swt.widgets.Text mNameText; 837 private org.eclipse.swt.widgets.Text mValueText; 838 private String mInitialName; 839 private String mName; 840 private String mValue; 841 private ResourceNameValidator mValidator; 842 NameValueDialog(Shell parent, String initialName)843 public NameValueDialog(Shell parent, String initialName) { 844 super(parent); 845 mInitialName = initialName; 846 } 847 848 @Override createDialogArea(Composite parent)849 protected Control createDialogArea(Composite parent) { 850 Composite container = new Composite(parent, SWT.NONE); 851 container.setLayout(new GridLayout(2, false)); 852 GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1); 853 // Wide enough to accommodate the error label 854 gridData.widthHint = 500; 855 container.setLayoutData(gridData); 856 857 858 Label nameLabel = new Label(container, SWT.NONE); 859 nameLabel.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1)); 860 nameLabel.setText("Name:"); 861 862 mNameText = new org.eclipse.swt.widgets.Text(container, SWT.BORDER); 863 mNameText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1)); 864 if (mInitialName != null) { 865 mNameText.setText(mInitialName); 866 mNameText.selectAll(); 867 } 868 869 Label valueLabel = new Label(container, SWT.NONE); 870 valueLabel.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1)); 871 valueLabel.setText("Value:"); 872 873 mValueText = new org.eclipse.swt.widgets.Text(container, SWT.BORDER); 874 mValueText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1)); 875 876 mNameText.addListener(SWT.Modify, this); 877 mValueText.addListener(SWT.Modify, this); 878 879 validate(); 880 881 return container; 882 } 883 884 @Override computeResult()885 protected void computeResult() { 886 mName = mNameText.getText().trim(); 887 mValue = mValueText.getText().trim(); 888 } 889 getName()890 private String getName() { 891 return mName; 892 } 893 getValue()894 private String getValue() { 895 return mValue; 896 } 897 898 @Override handleEvent(Event event)899 public void handleEvent(Event event) { 900 validate(); 901 } 902 validate()903 private void validate() { 904 IStatus status; 905 computeResult(); 906 if (mName.length() == 0) { 907 status = new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, "Enter a name"); 908 } else if (mValue.length() == 0) { 909 status = new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, "Enter a value"); 910 } else { 911 if (mValidator == null) { 912 mValidator = ResourceNameValidator.create(false, mProject, mResourceType); 913 } 914 String error = mValidator.isValid(mName); 915 if (error != null) { 916 status = new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, error); 917 } else { 918 status = new Status(IStatus.OK, AdtPlugin.PLUGIN_ID, null); 919 } 920 } 921 updateStatus(status); 922 } 923 } 924 925 /** 926 * Open the resource chooser for the given type, associated with the given 927 * editor 928 * 929 * @param graphicalEditor the editor associated with the resource to be 930 * chosen (used to find the associated Android target to be used 931 * for framework resources etc) 932 * @param type the resource type to be chosen 933 * @param currentValue the current value, or null 934 * @param validator a validator to be used, or null 935 * @return the chosen resource, null if cancelled and "" if value should be 936 * cleared 937 */ chooseResource( @onNull GraphicalEditorPart graphicalEditor, @NonNull ResourceType type, String currentValue, IInputValidator validator)938 public static String chooseResource( 939 @NonNull GraphicalEditorPart graphicalEditor, 940 @NonNull ResourceType type, 941 String currentValue, IInputValidator validator) { 942 ResourceChooser chooser = create(graphicalEditor, type). 943 setCurrentResource(currentValue); 944 if (validator != null) { 945 // Ensure wide enough to accommodate validator error message 946 chooser.setSize(85, 10); 947 chooser.setInputValidator(validator); 948 } 949 int result = chooser.open(); 950 if (result == ResourceChooser.CLEAR_RETURN_CODE) { 951 return ""; //$NON-NLS-1$ 952 } else if (result == Window.OK) { 953 return chooser.getCurrentResource(); 954 } 955 956 return null; 957 } 958 } 959