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