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
17package com.android.ide.eclipse.adt.internal.ui;
18
19import static com.android.SdkConstants.ANDROID_PREFIX;
20import static com.android.SdkConstants.PREFIX_RESOURCE_REF;
21
22import com.android.annotations.NonNull;
23import com.android.annotations.Nullable;
24import com.android.ide.common.rendering.api.ResourceValue;
25import com.android.ide.common.resources.ResourceItem;
26import com.android.ide.common.resources.ResourceRepository;
27import com.android.ide.common.resources.ResourceResolver;
28import com.android.ide.eclipse.adt.AdtPlugin;
29import com.android.ide.eclipse.adt.AdtUtils;
30import com.android.ide.eclipse.adt.internal.assetstudio.OpenCreateAssetSetWizardAction;
31import com.android.ide.eclipse.adt.internal.editors.layout.gle2.GraphicalEditorPart;
32import com.android.ide.eclipse.adt.internal.editors.layout.properties.PropertyFactory;
33import com.android.ide.eclipse.adt.internal.refactorings.extractstring.ExtractStringRefactoring;
34import com.android.ide.eclipse.adt.internal.refactorings.extractstring.ExtractStringWizard;
35import com.android.ide.eclipse.adt.internal.resources.ResourceHelper;
36import com.android.ide.eclipse.adt.internal.resources.ResourceNameValidator;
37import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResources;
38import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager;
39import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
40import com.android.ide.eclipse.adt.internal.sdk.ProjectState;
41import com.android.ide.eclipse.adt.internal.sdk.Sdk;
42import com.android.resources.ResourceType;
43import com.android.utils.Pair;
44import com.google.common.collect.Maps;
45
46import org.eclipse.core.resources.IFile;
47import org.eclipse.core.resources.IProject;
48import org.eclipse.core.resources.IResource;
49import org.eclipse.core.runtime.IStatus;
50import org.eclipse.core.runtime.Status;
51import org.eclipse.jface.dialogs.IDialogConstants;
52import org.eclipse.jface.dialogs.IInputValidator;
53import org.eclipse.jface.dialogs.InputDialog;
54import org.eclipse.jface.text.IRegion;
55import org.eclipse.jface.window.Window;
56import org.eclipse.ltk.ui.refactoring.RefactoringWizard;
57import org.eclipse.ltk.ui.refactoring.RefactoringWizardOpenOperation;
58import org.eclipse.swt.SWT;
59import org.eclipse.swt.events.ModifyEvent;
60import org.eclipse.swt.events.ModifyListener;
61import org.eclipse.swt.events.SelectionAdapter;
62import org.eclipse.swt.events.SelectionEvent;
63import org.eclipse.swt.layout.GridData;
64import org.eclipse.swt.layout.GridLayout;
65import org.eclipse.swt.widgets.Button;
66import org.eclipse.swt.widgets.Composite;
67import org.eclipse.swt.widgets.Control;
68import org.eclipse.swt.widgets.Event;
69import org.eclipse.swt.widgets.Label;
70import org.eclipse.swt.widgets.Listener;
71import org.eclipse.swt.widgets.Shell;
72import org.eclipse.swt.widgets.Text;
73import org.eclipse.ui.IWorkbench;
74import org.eclipse.ui.PlatformUI;
75import org.eclipse.ui.dialogs.AbstractElementListSelectionDialog;
76import org.eclipse.ui.dialogs.SelectionStatusDialog;
77
78import java.util.ArrayList;
79import java.util.Arrays;
80import java.util.Collection;
81import java.util.Collections;
82import java.util.List;
83import java.util.Locale;
84import java.util.Map;
85import java.util.regex.Matcher;
86import java.util.regex.Pattern;
87
88/**
89 * A dialog to let the user select a resource based on a resource type.
90 */
91public 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     */
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
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
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     */
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     */
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     */
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     */
288    public ResourceChooser setInitialSize(int width, int height) {
289        setSize(width, height);
290
291        return this;
292    }
293
294    @Override
295    public void create() {
296        super.create();
297
298        if (mShowValueText) {
299            mEditValueText.selectAll();
300            mEditValueText.setFocus();
301        }
302    }
303
304    @Override
305    protected void createButtonsForButtonBar(Composite parent) {
306        createButton(parent, CLEAR_BUTTON_ID, "Clear", false /*defaultButton*/);
307        super.createButtonsForButtonBar(parent);
308    }
309
310    @Override
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     */
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
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     */
353    public ResourceChooser setInputValidator(@Nullable IInputValidator inputValidator) {
354        mInputValidator = inputValidator;
355
356        return this;
357    }
358
359    @Override
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
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
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     */
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     */
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     */
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
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
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
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
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
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
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     */
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     */
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     */
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
812    private void updateNewButton(boolean isSystem) {
813        mNewButton.setEnabled(!isSystem && ResourceHelper.canCreateResourceType(mResourceType));
814    }
815
816    // ---- Implements ModifyListener ----
817
818    @Override
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
843        public NameValueDialog(Shell parent, String initialName) {
844            super(parent);
845            mInitialName = initialName;
846        }
847
848        @Override
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
885        protected void computeResult() {
886            mName = mNameText.getText().trim();
887            mValue = mValueText.getText().trim();
888        }
889
890        private String getName() {
891            return mName;
892        }
893
894        private String getValue() {
895            return mValue;
896        }
897
898        @Override
899        public void handleEvent(Event event) {
900            validate();
901        }
902
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     */
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