Home | History | Annotate | Download | only in refactoring
      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