Home | History | Annotate | Download | only in ui
      1 /*
      2  * Copyright (C) 2008 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 com.android.ide.common.resources.ResourceItem;
     20 import com.android.ide.common.resources.ResourceRepository;
     21 import com.android.ide.eclipse.adt.AdtPlugin;
     22 import com.android.ide.eclipse.adt.internal.editors.layout.properties.PropertyFactory;
     23 import com.android.ide.eclipse.adt.internal.refactorings.extractstring.ExtractStringRefactoring;
     24 import com.android.ide.eclipse.adt.internal.refactorings.extractstring.ExtractStringWizard;
     25 import com.android.resources.ResourceType;
     26 
     27 import org.eclipse.core.resources.IProject;
     28 import org.eclipse.core.runtime.IStatus;
     29 import org.eclipse.core.runtime.Status;
     30 import org.eclipse.jface.dialogs.DialogSettings;
     31 import org.eclipse.jface.dialogs.IDialogConstants;
     32 import org.eclipse.jface.dialogs.IDialogSettings;
     33 import org.eclipse.jface.viewers.ISelection;
     34 import org.eclipse.jface.viewers.TreePath;
     35 import org.eclipse.jface.viewers.TreeSelection;
     36 import org.eclipse.jface.viewers.TreeViewer;
     37 import org.eclipse.ltk.ui.refactoring.RefactoringWizard;
     38 import org.eclipse.ltk.ui.refactoring.RefactoringWizardOpenOperation;
     39 import org.eclipse.swt.SWT;
     40 import org.eclipse.swt.events.SelectionAdapter;
     41 import org.eclipse.swt.events.SelectionEvent;
     42 import org.eclipse.swt.events.SelectionListener;
     43 import org.eclipse.swt.layout.GridData;
     44 import org.eclipse.swt.widgets.Button;
     45 import org.eclipse.swt.widgets.Composite;
     46 import org.eclipse.swt.widgets.Control;
     47 import org.eclipse.swt.widgets.Shell;
     48 import org.eclipse.swt.widgets.Tree;
     49 import org.eclipse.ui.IWorkbench;
     50 import org.eclipse.ui.PlatformUI;
     51 import org.eclipse.ui.dialogs.FilteredTree;
     52 import org.eclipse.ui.dialogs.PatternFilter;
     53 import org.eclipse.ui.dialogs.SelectionStatusDialog;
     54 
     55 import java.util.Collection;
     56 import java.util.regex.Matcher;
     57 import java.util.regex.Pattern;
     58 
     59 /**
     60  * A dialog to let the user choose a reference to a resource.
     61  *
     62  */
     63 public class ReferenceChooserDialog extends SelectionStatusDialog {
     64 
     65     private static Pattern sResourcePattern = Pattern.compile("@(.*)/(.+)"); //$NON-NLS-1$
     66     private static Pattern sInlineIdResourcePattern = Pattern.compile("@\\+id/(.+)"); //$NON-NLS-1$
     67 
     68     private static IDialogSettings sDialogSettings = new DialogSettings("");
     69 
     70     private ResourceRepository mProjectResources;
     71     private String mCurrentResource;
     72     private FilteredTree mFilteredTree;
     73     private Button mNewResButton;
     74     private final IProject mProject;
     75     private TreeViewer mTreeViewer;
     76     private ResourcePreviewHelper mPreviewHelper;
     77 
     78     /**
     79      * @param project
     80      * @param parent
     81      */
     82     public ReferenceChooserDialog(IProject project, ResourceRepository projectResources,
     83             Shell parent) {
     84         super(parent);
     85         mProject = project;
     86         mProjectResources = projectResources;
     87 
     88         int shellStyle = getShellStyle();
     89         setShellStyle(shellStyle | SWT.MAX | SWT.RESIZE);
     90 
     91         setTitle("Reference Chooser");
     92         setMessage(String.format("Choose a resource"));
     93 
     94         setDialogBoundsSettings(sDialogSettings, getDialogBoundsStrategy());
     95     }
     96 
     97     public void setPreviewHelper(ResourcePreviewHelper previewHelper) {
     98         mPreviewHelper = previewHelper;
     99     }
    100 
    101     public void setCurrentResource(String resource) {
    102         mCurrentResource = resource;
    103     }
    104 
    105     public String getCurrentResource() {
    106         return mCurrentResource;
    107     }
    108 
    109 
    110     /* (non-Javadoc)
    111      * @see org.eclipse.ui.dialogs.SelectionStatusDialog#computeResult()
    112      */
    113     @Override
    114     protected void computeResult() {
    115         // get the selection
    116         TreePath treeSelection = getSelection();
    117         if (treeSelection != null) {
    118             if (treeSelection.getSegmentCount() == 2) {
    119                 // get the resource type and the resource item
    120                 ResourceType resourceType = (ResourceType)treeSelection.getFirstSegment();
    121                 ResourceItem resourceItem = (ResourceItem)treeSelection.getLastSegment();
    122 
    123                 mCurrentResource = resourceItem.getXmlString(resourceType, false /* system */);
    124             }
    125         }
    126     }
    127 
    128     @Override
    129     protected Control createDialogArea(Composite parent) {
    130         Composite top = (Composite)super.createDialogArea(parent);
    131 
    132         // create the standard message area
    133         createMessageArea(top);
    134 
    135         // create the filtered tree
    136         createFilteredTree(top);
    137 
    138         // setup the initial selection
    139         if (mCurrentResource != null) {
    140             setupInitialSelection();
    141         }
    142 
    143         // create the "New Resource" button
    144         createNewResButtons(top);
    145 
    146         Composite workaround = PropertyFactory.addWorkaround(top);
    147         if (workaround != null) {
    148             workaround.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false, 1, 1));
    149         }
    150 
    151         return top;
    152     }
    153 
    154     /**
    155      * Creates the "New Resource" button.
    156      * @param top the parent composite
    157      */
    158     private void createNewResButtons(Composite top) {
    159         mNewResButton = new Button(top, SWT.NONE);
    160         mNewResButton.addSelectionListener(new OnNewResButtonSelected());
    161         updateNewResButton();
    162     }
    163 
    164     private void createFilteredTree(Composite parent) {
    165         mFilteredTree = new FilteredTree(parent, SWT.BORDER | SWT.SINGLE | SWT.FULL_SELECTION,
    166                 new PatternFilter());
    167 
    168         GridData data = new GridData();
    169         data.widthHint = convertWidthInCharsToPixels(60);
    170         data.heightHint = convertHeightInCharsToPixels(18);
    171         data.grabExcessVerticalSpace = true;
    172         data.grabExcessHorizontalSpace = true;
    173         data.horizontalAlignment = GridData.FILL;
    174         data.verticalAlignment = GridData.FILL;
    175         mFilteredTree.setLayoutData(data);
    176         mFilteredTree.setFont(parent.getFont());
    177 
    178         mTreeViewer = mFilteredTree.getViewer();
    179         Tree tree = mTreeViewer.getTree();
    180 
    181         tree.addSelectionListener(new SelectionListener() {
    182             @Override
    183             public void widgetDefaultSelected(SelectionEvent e) {
    184                 handleDoubleClick();
    185             }
    186 
    187             @Override
    188             public void widgetSelected(SelectionEvent e) {
    189                 handleSelection();
    190             }
    191         });
    192 
    193         mTreeViewer.setLabelProvider(new ResourceLabelProvider());
    194         mTreeViewer.setContentProvider(new ResourceContentProvider(false /* fullLevels */));
    195         mTreeViewer.setInput(mProjectResources);
    196     }
    197 
    198     protected void handleSelection() {
    199         validateCurrentSelection();
    200         updateNewResButton();
    201 
    202         if (mPreviewHelper != null) {
    203             TreePath treeSelection = getSelection();
    204             ResourceType type = null;
    205             if (treeSelection != null && treeSelection.getSegmentCount() == 2) {
    206                 Object segment = treeSelection.getSegment(0);
    207                 if (segment instanceof ResourceType) {
    208                     type = (ResourceType) segment;
    209                     // Ensure that mCurrentResource is valid
    210                     computeResult();
    211                 }
    212             }
    213 
    214             mPreviewHelper.updatePreview(type, mCurrentResource);
    215         }
    216     }
    217 
    218     protected void handleDoubleClick() {
    219         if (validateCurrentSelection()) {
    220             buttonPressed(IDialogConstants.OK_ID);
    221         }
    222     }
    223 
    224     /**
    225      * Returns the selected item in the tree as a {@link TreePath} object.
    226      * @return the <code>TreePath</code> object or <code>null</code> if there was no selection.
    227      */
    228     private TreePath getSelection() {
    229         ISelection selection = mFilteredTree.getViewer().getSelection();
    230         if (selection instanceof TreeSelection) {
    231             TreeSelection treeSelection = (TreeSelection)selection;
    232             TreePath[] treePaths = treeSelection.getPaths();
    233 
    234             // the selection mode is SWT.SINGLE, so we just get the first one.
    235             if (treePaths.length > 0) {
    236                 return treePaths[0];
    237             }
    238         }
    239 
    240         return null;
    241     }
    242 
    243     private boolean validateCurrentSelection() {
    244         TreePath treeSelection = getSelection();
    245 
    246         IStatus status;
    247         if (treeSelection != null) {
    248             if (treeSelection.getSegmentCount() == 2) {
    249                 status = new Status(IStatus.OK, AdtPlugin.PLUGIN_ID,
    250                         IStatus.OK, "", //$NON-NLS-1$
    251                         null);
    252             } else {
    253                 status = new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
    254                         IStatus.ERROR, "You must select a Resource Item",
    255                         null);
    256             }
    257         } else {
    258             status = new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
    259                     IStatus.ERROR, "", //$NON-NLS-1$
    260                     null);
    261         }
    262 
    263         updateStatus(status);
    264 
    265         return status.isOK();
    266     }
    267 
    268     /**
    269      * Updates the new res button when the list selection changes.
    270      * The name of the button changes depending on the resource.
    271      */
    272     private void updateNewResButton() {
    273         ResourceType type = getSelectedResourceType();
    274 
    275         // We only support adding new strings right now
    276         mNewResButton.setEnabled(type == ResourceType.STRING);
    277 
    278         String title = String.format("New %1$s...",
    279                 type == null ? "Resource" : type.getDisplayName());
    280         mNewResButton.setText(title);
    281         mNewResButton.pack();
    282     }
    283 
    284     /**
    285      * Callback invoked when the mNewResButton is selected by the user.
    286      */
    287     private class OnNewResButtonSelected extends SelectionAdapter {
    288         @Override
    289          public void widgetSelected(SelectionEvent e) {
    290              super.widgetSelected(e);
    291 
    292              ResourceType type = getSelectedResourceType();
    293 
    294              // We currently only support strings
    295              if (type == ResourceType.STRING) {
    296 
    297                  ExtractStringRefactoring ref = new ExtractStringRefactoring(
    298                          mProject, true /*enforceNew*/);
    299                  RefactoringWizard wizard = new ExtractStringWizard(ref, mProject);
    300                  RefactoringWizardOpenOperation op = new RefactoringWizardOpenOperation(wizard);
    301                  try {
    302                      IWorkbench w = PlatformUI.getWorkbench();
    303                      if (op.run(w.getDisplay().getActiveShell(), wizard.getDefaultPageTitle()) ==
    304                              IDialogConstants.OK_ID) {
    305                          mTreeViewer.refresh();
    306 
    307                          // select it if possible
    308                          setupInitialSelection(type, ref.getXmlStringId());
    309                      }
    310                  } catch (InterruptedException ex) {
    311                      // Interrupted. Pass.
    312                  }
    313              }
    314          }
    315      }
    316 
    317     /**
    318      * Returns the {@link ResourceType} of the selected element, if any.
    319      * Returns null if nothing suitable is selected.
    320      */
    321     private ResourceType getSelectedResourceType() {
    322         ResourceType type = null;
    323 
    324         TreePath selection = getSelection();
    325         if (selection != null && selection.getSegmentCount() > 0) {
    326             Object first = selection.getFirstSegment();
    327             if (first instanceof ResourceType) {
    328                 type = (ResourceType) first;
    329             }
    330         }
    331         return type;
    332     }
    333 
    334     /**
    335      * Sets up the initial selection.
    336      * <p/>
    337      * This parses {@link #mCurrentResource} to find out the resource type and the resource name.
    338      */
    339     private void setupInitialSelection() {
    340         // checks the inline id pattern first as it's more restrictive than the other one.
    341         Matcher m = sInlineIdResourcePattern.matcher(mCurrentResource);
    342         if (m.matches()) {
    343             // get the matching name
    344             String resourceName = m.group(1);
    345 
    346             // setup initial selection
    347             setupInitialSelection(ResourceType.ID, resourceName);
    348         } else {
    349             // attempts the inline id pattern
    350             m = sResourcePattern.matcher(mCurrentResource);
    351             if (m.matches()) {
    352                 // get the resource type.
    353                 ResourceType resourceType = ResourceType.getEnum(m.group(1));
    354                 if (resourceType != null) {
    355                     // get the matching name
    356                     String resourceName = m.group(2);
    357 
    358                     // setup initial selection
    359                     setupInitialSelection(resourceType, resourceName);
    360                 }
    361             }
    362         }
    363     }
    364 
    365     /**
    366      * Sets up the initial selection based on a {@link ResourceType} and a resource name.
    367      * @param resourceType the resource type.
    368      * @param resourceName the resource name.
    369      */
    370     private void setupInitialSelection(ResourceType resourceType, String resourceName) {
    371         // get all the resources of this type
    372         Collection<ResourceItem> resourceItems =
    373                 mProjectResources.getResourceItemsOfType(resourceType);
    374 
    375         for (ResourceItem resourceItem : resourceItems) {
    376             if (resourceName.equals(resourceItem.getName())) {
    377                 // name of the resource match, we select it,
    378                 TreePath treePath = new TreePath(new Object[] { resourceType, resourceItem });
    379                 mFilteredTree.getViewer().setSelection(
    380                         new TreeSelection(treePath),
    381                         true /*reveal*/);
    382 
    383                 // and we're done.
    384                 return;
    385             }
    386         }
    387 
    388         // if we get here, the resource type is valid, but the resource is missing.
    389         // we select and expand the resource type element.
    390         TreePath treePath = new TreePath(new Object[] { resourceType });
    391         mFilteredTree.getViewer().setSelection(
    392                 new TreeSelection(treePath),
    393                 true /*reveal*/);
    394         mFilteredTree.getViewer().setExpandedState(resourceType, true /* expanded */);
    395     }
    396 }
    397