1 /* 2 * Copyright (C) 2011 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.editors.layout.refactoring; 18 19 import static org.eclipse.jface.viewers.StyledString.DECORATIONS_STYLER; 20 import static org.eclipse.jface.viewers.StyledString.QUALIFIER_STYLER; 21 22 import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditor; 23 import com.android.ide.eclipse.adt.internal.resources.ResourceNameValidator; 24 import com.android.resources.ResourceType; 25 import com.android.util.Pair; 26 27 import org.eclipse.core.resources.IProject; 28 import org.eclipse.jface.viewers.CheckStateChangedEvent; 29 import org.eclipse.jface.viewers.CheckboxTableViewer; 30 import org.eclipse.jface.viewers.ICheckStateListener; 31 import org.eclipse.jface.viewers.IStructuredContentProvider; 32 import org.eclipse.jface.viewers.StyledCellLabelProvider; 33 import org.eclipse.jface.viewers.StyledString; 34 import org.eclipse.jface.viewers.Viewer; 35 import org.eclipse.jface.viewers.ViewerCell; 36 import org.eclipse.swt.SWT; 37 import org.eclipse.swt.events.SelectionAdapter; 38 import org.eclipse.swt.events.SelectionEvent; 39 import org.eclipse.swt.layout.GridData; 40 import org.eclipse.swt.layout.GridLayout; 41 import org.eclipse.swt.layout.RowLayout; 42 import org.eclipse.swt.widgets.Button; 43 import org.eclipse.swt.widgets.Composite; 44 import org.eclipse.swt.widgets.Label; 45 import org.eclipse.swt.widgets.Table; 46 import org.eclipse.swt.widgets.Text; 47 import org.w3c.dom.Attr; 48 49 import java.util.ArrayList; 50 import java.util.Collections; 51 import java.util.Comparator; 52 import java.util.HashMap; 53 import java.util.HashSet; 54 import java.util.List; 55 import java.util.Map; 56 import java.util.Set; 57 58 class ExtractStyleWizard extends VisualRefactoringWizard { 59 public ExtractStyleWizard(ExtractStyleRefactoring ref, LayoutEditor editor) { 60 super(ref, editor); 61 setDefaultPageTitle(ref.getName()); 62 } 63 64 @Override 65 protected void addUserInputPages() { 66 String initialName = "styleName"; 67 addPage(new InputPage(mEditor.getProject(), initialName)); 68 } 69 70 /** 71 * Wizard page which inputs parameters for the {@link ExtractStyleRefactoring} 72 * operation 73 */ 74 private static class InputPage extends VisualRefactoringInputPage { 75 private final IProject mProject; 76 private final String mSuggestedName; 77 private Text mNameText; 78 private Table mTable; 79 private Button mRemoveExtracted; 80 private Button mSetStyle; 81 private Button mRemoveAll; 82 private Button mExtend;; 83 private CheckboxTableViewer mCheckedView; 84 85 private String mParentStyle; 86 private Set<Attr> mInSelection; 87 private List<Attr> mAllAttributes; 88 private int mElementCount; 89 private Map<Attr, Integer> mFrequencyCount; 90 private Set<Attr> mShown; 91 private List<Attr> mInitialChecked; 92 private List<Map.Entry<String, List<Attr>>> mRoot; 93 private Map<String, List<Attr>> mAvailableAttributes; 94 95 public InputPage(IProject project, String suggestedName) { 96 super("ExtractStyleInputPage"); 97 mProject = project; 98 mSuggestedName = suggestedName; 99 } 100 101 public void createControl(Composite parent) { 102 initialize(); 103 104 Composite composite = new Composite(parent, SWT.NONE); 105 composite.setLayout(new GridLayout(2, false)); 106 107 Label nameLabel = new Label(composite, SWT.NONE); 108 nameLabel.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1)); 109 nameLabel.setText("Style Name:"); 110 111 mNameText = new Text(composite, SWT.BORDER); 112 mNameText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1)); 113 mNameText.addModifyListener(mModifyValidateListener); 114 115 mRemoveExtracted = new Button(composite, SWT.CHECK); 116 mRemoveExtracted.setSelection(true); 117 mRemoveExtracted.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false, 2, 1)); 118 mRemoveExtracted.setText("Remove extracted attributes"); 119 mRemoveExtracted.addSelectionListener(mSelectionValidateListener); 120 121 mRemoveAll = new Button(composite, SWT.CHECK); 122 mRemoveAll.setSelection(false); 123 mRemoveAll.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false, 2, 1)); 124 mRemoveAll.setText("Remove all extracted attributes regardless of value"); 125 mRemoveAll.addSelectionListener(mSelectionValidateListener); 126 127 boolean defaultSetStyle = false; 128 if (mParentStyle != null) { 129 mExtend = new Button(composite, SWT.CHECK); 130 mExtend.setSelection(true); 131 mExtend.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false, 2, 1)); 132 mExtend.setText(String.format("Extend %1$s", mParentStyle)); 133 mExtend.addSelectionListener(mSelectionValidateListener); 134 defaultSetStyle = true; 135 } 136 137 mSetStyle = new Button(composite, SWT.CHECK); 138 mSetStyle.setSelection(defaultSetStyle); 139 mSetStyle.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false, 2, 1)); 140 mSetStyle.setText("Set style attribute on extracted elements"); 141 mSetStyle.addSelectionListener(mSelectionValidateListener); 142 143 new Label(composite, SWT.NONE); 144 new Label(composite, SWT.NONE); 145 146 Label tableLabel = new Label(composite, SWT.NONE); 147 tableLabel.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 2, 1)); 148 tableLabel.setText("Choose style attributes to extract:"); 149 150 mCheckedView = CheckboxTableViewer.newCheckList(composite, SWT.BORDER 151 | SWT.FULL_SELECTION | SWT.HIDE_SELECTION); 152 mTable = mCheckedView.getTable(); 153 mTable.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 2, 2)); 154 ((GridData) mTable.getLayoutData()).heightHint = 200; 155 156 mCheckedView.setContentProvider(new ArgumentContentProvider()); 157 mCheckedView.setLabelProvider(new ArgumentLabelProvider()); 158 mCheckedView.setInput(mRoot); 159 final Object[] initialSelection = mInitialChecked.toArray(); 160 mCheckedView.setCheckedElements(initialSelection); 161 162 mCheckedView.addCheckStateListener(new ICheckStateListener() { 163 public void checkStateChanged(CheckStateChangedEvent event) { 164 // Try to disable other elements that conflict with this 165 boolean isChecked = event.getChecked(); 166 if (isChecked) { 167 Attr attribute = (Attr) event.getElement(); 168 List<Attr> list = mAvailableAttributes.get(attribute.getLocalName()); 169 for (Attr other : list) { 170 if (other != attribute && mShown.contains(other)) { 171 mCheckedView.setChecked(other, false); 172 } 173 } 174 } 175 176 validatePage(); 177 } 178 }); 179 180 // Select All / Deselect All 181 Composite buttonForm = new Composite(composite, SWT.NONE); 182 buttonForm.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 2, 1)); 183 RowLayout rowLayout = new RowLayout(SWT.HORIZONTAL); 184 rowLayout.marginTop = 0; 185 rowLayout.marginLeft = 0; 186 buttonForm.setLayout(rowLayout); 187 Button checkAllButton = new Button(buttonForm, SWT.FLAT); 188 checkAllButton.setText("Select All"); 189 checkAllButton.addSelectionListener(new SelectionAdapter() { 190 @Override 191 public void widgetSelected(SelectionEvent e) { 192 // Select "all" (but not conflicting settings) 193 mCheckedView.setCheckedElements(initialSelection); 194 } 195 }); 196 Button uncheckAllButton = new Button(buttonForm, SWT.FLAT); 197 uncheckAllButton.setText("Deselect All"); 198 uncheckAllButton.addSelectionListener(new SelectionAdapter() { 199 @Override 200 public void widgetSelected(SelectionEvent e) { 201 mCheckedView.setAllChecked(false); 202 } 203 }); 204 205 // Initialize UI: 206 if (mSuggestedName != null) { 207 mNameText.setText(mSuggestedName); 208 } 209 210 setControl(composite); 211 validatePage(); 212 } 213 214 private void initialize() { 215 ExtractStyleRefactoring ref = (ExtractStyleRefactoring) getRefactoring(); 216 217 mElementCount = ref.getElements().size(); 218 219 mParentStyle = ref.getParentStyle(); 220 221 // Set up data structures needed by the wizard -- to compute the actual 222 // attributes to list in the wizard (there could be multiple attributes 223 // of the same name (on different elements) and we only want to show one, etc.) 224 225 Pair<Map<String, List<Attr>>, Set<Attr>> result = ref.getAvailableAttributes(); 226 // List of all available attributes on the selected elements 227 mAvailableAttributes = result.getFirst(); 228 // Set of attributes that overlap the text selection, or all attributes if 229 // wizard is invoked from GUI context 230 mInSelection = result.getSecond(); 231 232 // The root data structure, which we set as the table root. The content provider 233 // will produce children from it. This is the entry set of a map from 234 // attribute name to list of attribute nodes for that attribute name. 235 mRoot = new ArrayList<Map.Entry<String, List<Attr>>>( 236 mAvailableAttributes.entrySet()); 237 238 // Sort the items by attribute name -- the attribute name is the key 239 // in the entry set above. 240 Collections.sort(mRoot, new Comparator<Map.Entry<String, List<Attr>>>() { 241 public int compare(Map.Entry<String, List<Attr>> e1, 242 Map.Entry<String, List<Attr>> e2) { 243 return e1.getKey().compareTo(e2.getKey()); 244 } 245 }); 246 247 // Set of attributes actually included in the list shown to the user. 248 // (There could be many additional "aliasing" nodes on other elements 249 // with the same name.) Note however that we DO show multiple attribute 250 // occurrences of the same attribute name: precisely one for each unique -value- 251 // of that attribute. 252 mShown = new HashSet<Attr>(); 253 254 // The list of initially checked attributes. 255 mInitialChecked = new ArrayList<Attr>(); 256 257 // All attributes. 258 mAllAttributes = new ArrayList<Attr>(); 259 260 // Frequency count, from attribute to integer. Attributes that do not 261 // appear in the list have frequency 1, not 0. 262 mFrequencyCount = new HashMap<Attr, Integer>(); 263 264 for (Map.Entry<String, List<Attr>> entry : mRoot) { 265 // Iterate over all attributes of the same name, and sort them 266 // by value. This will make it easy to list each -unique- value in the 267 // wizard. 268 List<Attr> attrList = entry.getValue(); 269 Collections.sort(attrList, new Comparator<Attr>() { 270 public int compare(Attr a1, Attr a2) { 271 return a1.getValue().compareTo(a2.getValue()); 272 } 273 }); 274 275 // We need to compute a couple of things: the frequency for all identical 276 // values (and stash them in the frequency map), and record the first 277 // attribute with a particular value into the list of attributes to 278 // be shown. 279 Attr prevAttr = null; 280 String prev = null; 281 List<Attr> uniqueValueAttrs = new ArrayList<Attr>(); 282 for (Attr attr : attrList) { 283 String value = attr.getValue(); 284 if (value.equals(prev)) { 285 Integer count = mFrequencyCount.get(prevAttr); 286 if (count == null) { 287 count = Integer.valueOf(2); 288 } else { 289 count = Integer.valueOf(count.intValue() + 1); 290 } 291 mFrequencyCount.put(prevAttr, count); 292 } else { 293 uniqueValueAttrs.add(attr); 294 prev = value; 295 prevAttr = attr; 296 } 297 } 298 299 // Sort the values by frequency (and for equal frequencies, alphabetically 300 // by value) 301 Collections.sort(uniqueValueAttrs, new Comparator<Attr>() { 302 public int compare(Attr a1, Attr a2) { 303 Integer f1 = mFrequencyCount.get(a1); 304 Integer f2 = mFrequencyCount.get(a2); 305 if (f1 == null) { 306 f1 = Integer.valueOf(1); 307 } 308 if (f2 == null) { 309 f2 = Integer.valueOf(1); 310 } 311 int delta = f2.intValue() - f1.intValue(); 312 if (delta != 0) { 313 return delta; 314 } else { 315 return a1.getValue().compareTo(a2.getValue()); 316 } 317 } 318 }); 319 320 // Add the items in order, and select those attributes that overlap 321 // the selection 322 mAllAttributes.addAll(uniqueValueAttrs); 323 mShown.addAll(uniqueValueAttrs); 324 Attr first = uniqueValueAttrs.get(0); 325 if (mInSelection.contains(first)) { 326 mInitialChecked.add(first); 327 } 328 } 329 } 330 331 @Override 332 protected boolean validatePage() { 333 boolean ok = true; 334 335 String text = mNameText.getText().trim(); 336 337 if (text.length() == 0) { 338 setErrorMessage("Provide a name for the new style"); 339 ok = false; 340 } else { 341 ResourceNameValidator validator = ResourceNameValidator.create(false, mProject, 342 ResourceType.STYLE); 343 String message = validator.isValid(text); 344 if (message != null) { 345 setErrorMessage(message); 346 ok = false; 347 } 348 } 349 350 Object[] checkedElements = mCheckedView.getCheckedElements(); 351 if (checkedElements.length == 0) { 352 setErrorMessage("Choose at least one attribute to extract"); 353 ok = false; 354 } 355 356 if (ok) { 357 setErrorMessage(null); 358 359 // Record state 360 ExtractStyleRefactoring refactoring = (ExtractStyleRefactoring) getRefactoring(); 361 refactoring.setStyleName(text); 362 refactoring.setRemoveExtracted(mRemoveExtracted.getSelection()); 363 refactoring.setRemoveAll(mRemoveAll.getSelection()); 364 refactoring.setApplyStyle(mSetStyle.getSelection()); 365 if (mExtend != null && mExtend.getSelection()) { 366 refactoring.setParent(mParentStyle); 367 } 368 List<Attr> attributes = new ArrayList<Attr>(); 369 for (Object o : checkedElements) { 370 attributes.add((Attr) o); 371 } 372 refactoring.setChosenAttributes(attributes); 373 } 374 375 setPageComplete(ok); 376 return ok; 377 } 378 379 private class ArgumentLabelProvider extends StyledCellLabelProvider { 380 public ArgumentLabelProvider() { 381 } 382 383 @Override 384 public void update(ViewerCell cell) { 385 Object element = cell.getElement(); 386 Attr attribute = (Attr) element; 387 388 StyledString styledString = new StyledString(); 389 styledString.append(attribute.getLocalName()); 390 styledString.append(" = ", QUALIFIER_STYLER); 391 styledString.append(attribute.getValue()); 392 393 if (mElementCount > 1) { 394 Integer f = mFrequencyCount.get(attribute); 395 String s = String.format(" (in %d/%d elements)", 396 f != null ? f.intValue(): 1, mElementCount); 397 styledString.append(s, DECORATIONS_STYLER); 398 } 399 cell.setText(styledString.toString()); 400 cell.setStyleRanges(styledString.getStyleRanges()); 401 super.update(cell); 402 } 403 } 404 405 private class ArgumentContentProvider implements IStructuredContentProvider { 406 public ArgumentContentProvider() { 407 } 408 409 public Object[] getElements(Object inputElement) { 410 if (inputElement == mRoot) { 411 return mAllAttributes.toArray(); 412 } 413 414 return new Object[0]; 415 } 416 417 public void dispose() { 418 } 419 420 public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { 421 } 422 } 423 } 424 } 425