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