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