diff options
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ExtractStyleWizard.java')
-rw-r--r-- | eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ExtractStyleWizard.java | 440 |
1 files changed, 440 insertions, 0 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ExtractStyleWizard.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ExtractStyleWizard.java new file mode 100644 index 000000000..187452d21 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ExtractStyleWizard.java @@ -0,0 +1,440 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.eclipse.org/org/documents/epl-v10.php + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.ide.eclipse.adt.internal.editors.layout.refactoring; + +import static org.eclipse.jface.viewers.StyledString.DECORATIONS_STYLER; +import static org.eclipse.jface.viewers.StyledString.QUALIFIER_STYLER; + +import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate; +import com.android.ide.eclipse.adt.internal.resources.ResourceNameValidator; +import com.android.resources.ResourceType; +import com.android.utils.Pair; + +import org.eclipse.core.resources.IProject; +import org.eclipse.jface.viewers.CheckStateChangedEvent; +import org.eclipse.jface.viewers.CheckboxTableViewer; +import org.eclipse.jface.viewers.ICheckStateListener; +import org.eclipse.jface.viewers.IStructuredContentProvider; +import org.eclipse.jface.viewers.StyledCellLabelProvider; +import org.eclipse.jface.viewers.StyledString; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.jface.viewers.ViewerCell; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.layout.RowLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Table; +import org.eclipse.swt.widgets.Text; +import org.w3c.dom.Attr; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +class ExtractStyleWizard extends VisualRefactoringWizard { + public ExtractStyleWizard(ExtractStyleRefactoring ref, LayoutEditorDelegate editor) { + super(ref, editor); + setDefaultPageTitle(ref.getName()); + } + + @Override + protected void addUserInputPages() { + String initialName = "styleName"; + addPage(new InputPage(mDelegate.getEditor().getProject(), initialName)); + } + + /** + * Wizard page which inputs parameters for the {@link ExtractStyleRefactoring} + * operation + */ + private static class InputPage extends VisualRefactoringInputPage { + private final IProject mProject; + private final String mSuggestedName; + private Text mNameText; + private Table mTable; + private Button mRemoveExtracted; + private Button mSetStyle; + private Button mRemoveAll; + private Button mExtend; + private CheckboxTableViewer mCheckedView; + + private String mParentStyle; + private Set<Attr> mInSelection; + private List<Attr> mAllAttributes; + private int mElementCount; + private Map<Attr, Integer> mFrequencyCount; + private Set<Attr> mShown; + private List<Attr> mInitialChecked; + private List<Attr> mAllChecked; + private List<Map.Entry<String, List<Attr>>> mRoot; + private Map<String, List<Attr>> mAvailableAttributes; + + public InputPage(IProject project, String suggestedName) { + super("ExtractStyleInputPage"); + mProject = project; + mSuggestedName = suggestedName; + } + + @Override + public void createControl(Composite parent) { + initialize(); + + Composite composite = new Composite(parent, SWT.NONE); + composite.setLayout(new GridLayout(2, false)); + + Label nameLabel = new Label(composite, SWT.NONE); + nameLabel.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1)); + nameLabel.setText("Style Name:"); + + mNameText = new Text(composite, SWT.BORDER); + mNameText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1)); + mNameText.addModifyListener(mModifyValidateListener); + + mRemoveExtracted = new Button(composite, SWT.CHECK); + mRemoveExtracted.setSelection(true); + mRemoveExtracted.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false, 2, 1)); + mRemoveExtracted.setText("Remove extracted attributes"); + mRemoveExtracted.addSelectionListener(mSelectionValidateListener); + + mRemoveAll = new Button(composite, SWT.CHECK); + mRemoveAll.setSelection(false); + mRemoveAll.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false, 2, 1)); + mRemoveAll.setText("Remove all extracted attributes regardless of value"); + mRemoveAll.addSelectionListener(mSelectionValidateListener); + + boolean defaultSetStyle = false; + if (mParentStyle != null) { + mExtend = new Button(composite, SWT.CHECK); + mExtend.setSelection(true); + mExtend.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false, 2, 1)); + mExtend.setText(String.format("Extend %1$s", mParentStyle)); + mExtend.addSelectionListener(mSelectionValidateListener); + defaultSetStyle = true; + } + + mSetStyle = new Button(composite, SWT.CHECK); + mSetStyle.setSelection(defaultSetStyle); + mSetStyle.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false, 2, 1)); + mSetStyle.setText("Set style attribute on extracted elements"); + mSetStyle.addSelectionListener(mSelectionValidateListener); + + new Label(composite, SWT.NONE); + new Label(composite, SWT.NONE); + + Label tableLabel = new Label(composite, SWT.NONE); + tableLabel.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 2, 1)); + tableLabel.setText("Choose style attributes to extract:"); + + mCheckedView = CheckboxTableViewer.newCheckList(composite, SWT.BORDER + | SWT.FULL_SELECTION | SWT.HIDE_SELECTION); + mTable = mCheckedView.getTable(); + mTable.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 2, 2)); + ((GridData) mTable.getLayoutData()).heightHint = 200; + + mCheckedView.setContentProvider(new ArgumentContentProvider()); + mCheckedView.setLabelProvider(new ArgumentLabelProvider()); + mCheckedView.setInput(mRoot); + final Object[] initialSelection = mInitialChecked.toArray(); + mCheckedView.setCheckedElements(initialSelection); + + mCheckedView.addCheckStateListener(new ICheckStateListener() { + @Override + public void checkStateChanged(CheckStateChangedEvent event) { + // Try to disable other elements that conflict with this + boolean isChecked = event.getChecked(); + if (isChecked) { + Attr attribute = (Attr) event.getElement(); + List<Attr> list = mAvailableAttributes.get(attribute.getLocalName()); + for (Attr other : list) { + if (other != attribute && mShown.contains(other)) { + mCheckedView.setChecked(other, false); + } + } + } + + validatePage(); + } + }); + + // Select All / Deselect All + Composite buttonForm = new Composite(composite, SWT.NONE); + buttonForm.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 2, 1)); + RowLayout rowLayout = new RowLayout(SWT.HORIZONTAL); + rowLayout.marginTop = 0; + rowLayout.marginLeft = 0; + buttonForm.setLayout(rowLayout); + Button checkAllButton = new Button(buttonForm, SWT.FLAT); + checkAllButton.setText("Select All"); + checkAllButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + // Select "all" (but not conflicting settings) + mCheckedView.setCheckedElements(mAllChecked.toArray()); + validatePage(); + } + }); + Button uncheckAllButton = new Button(buttonForm, SWT.FLAT); + uncheckAllButton.setText("Deselect All"); + uncheckAllButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + mCheckedView.setAllChecked(false); + validatePage(); + } + }); + + // Initialize UI: + if (mSuggestedName != null) { + mNameText.setText(mSuggestedName); + } + + setControl(composite); + validatePage(); + } + + private void initialize() { + ExtractStyleRefactoring ref = (ExtractStyleRefactoring) getRefactoring(); + + mElementCount = ref.getElements().size(); + + mParentStyle = ref.getParentStyle(); + + // Set up data structures needed by the wizard -- to compute the actual + // attributes to list in the wizard (there could be multiple attributes + // of the same name (on different elements) and we only want to show one, etc.) + + Pair<Map<String, List<Attr>>, Set<Attr>> result = ref.getAvailableAttributes(); + // List of all available attributes on the selected elements + mAvailableAttributes = result.getFirst(); + // Set of attributes that overlap the text selection, or all attributes if + // wizard is invoked from GUI context + mInSelection = result.getSecond(); + + // The root data structure, which we set as the table root. The content provider + // will produce children from it. This is the entry set of a map from + // attribute name to list of attribute nodes for that attribute name. + mRoot = new ArrayList<Map.Entry<String, List<Attr>>>( + mAvailableAttributes.entrySet()); + + // Sort the items by attribute name -- the attribute name is the key + // in the entry set above. + Collections.sort(mRoot, new Comparator<Map.Entry<String, List<Attr>>>() { + @Override + public int compare(Map.Entry<String, List<Attr>> e1, + Map.Entry<String, List<Attr>> e2) { + return e1.getKey().compareTo(e2.getKey()); + } + }); + + // Set of attributes actually included in the list shown to the user. + // (There could be many additional "aliasing" nodes on other elements + // with the same name.) Note however that we DO show multiple attribute + // occurrences of the same attribute name: precisely one for each unique -value- + // of that attribute. + mShown = new HashSet<Attr>(); + + // The list of initially checked attributes. + mInitialChecked = new ArrayList<Attr>(); + + // The list of attributes to be checked if "Select All" is chosen (this is not + // the same as *all* attributes, since we need to exclude any conflicts) + mAllChecked = new ArrayList<Attr>(); + + // All attributes. + mAllAttributes = new ArrayList<Attr>(); + + // Frequency count, from attribute to integer. Attributes that do not + // appear in the list have frequency 1, not 0. + mFrequencyCount = new HashMap<Attr, Integer>(); + + for (Map.Entry<String, List<Attr>> entry : mRoot) { + // Iterate over all attributes of the same name, and sort them + // by value. This will make it easy to list each -unique- value in the + // wizard. + List<Attr> attrList = entry.getValue(); + Collections.sort(attrList, new Comparator<Attr>() { + @Override + public int compare(Attr a1, Attr a2) { + return a1.getValue().compareTo(a2.getValue()); + } + }); + + // We need to compute a couple of things: the frequency for all identical + // values (and stash them in the frequency map), and record the first + // attribute with a particular value into the list of attributes to + // be shown. + Attr prevAttr = null; + String prev = null; + List<Attr> uniqueValueAttrs = new ArrayList<Attr>(); + for (Attr attr : attrList) { + String value = attr.getValue(); + if (value.equals(prev)) { + Integer count = mFrequencyCount.get(prevAttr); + if (count == null) { + count = Integer.valueOf(2); + } else { + count = Integer.valueOf(count.intValue() + 1); + } + mFrequencyCount.put(prevAttr, count); + } else { + uniqueValueAttrs.add(attr); + prev = value; + prevAttr = attr; + } + } + + // Sort the values by frequency (and for equal frequencies, alphabetically + // by value) + Collections.sort(uniqueValueAttrs, new Comparator<Attr>() { + @Override + public int compare(Attr a1, Attr a2) { + Integer f1 = mFrequencyCount.get(a1); + Integer f2 = mFrequencyCount.get(a2); + if (f1 == null) { + f1 = Integer.valueOf(1); + } + if (f2 == null) { + f2 = Integer.valueOf(1); + } + int delta = f2.intValue() - f1.intValue(); + if (delta != 0) { + return delta; + } else { + return a1.getValue().compareTo(a2.getValue()); + } + } + }); + + // Add the items in order, and select those attributes that overlap + // the selection + mAllAttributes.addAll(uniqueValueAttrs); + mShown.addAll(uniqueValueAttrs); + Attr first = uniqueValueAttrs.get(0); + mAllChecked.add(first); + if (mInSelection.contains(first)) { + mInitialChecked.add(first); + } + } + } + + @Override + protected boolean validatePage() { + boolean ok = true; + + String text = mNameText.getText().trim(); + + if (text.length() == 0) { + setErrorMessage("Provide a name for the new style"); + ok = false; + } else { + ResourceNameValidator validator = ResourceNameValidator.create(false, mProject, + ResourceType.STYLE); + String message = validator.isValid(text); + if (message != null) { + setErrorMessage(message); + ok = false; + } + } + + Object[] checkedElements = mCheckedView.getCheckedElements(); + if (checkedElements.length == 0) { + setErrorMessage("Choose at least one attribute to extract"); + ok = false; + } + + if (ok) { + setErrorMessage(null); + + // Record state + ExtractStyleRefactoring refactoring = (ExtractStyleRefactoring) getRefactoring(); + refactoring.setStyleName(text); + refactoring.setRemoveExtracted(mRemoveExtracted.getSelection()); + refactoring.setRemoveAll(mRemoveAll.getSelection()); + refactoring.setApplyStyle(mSetStyle.getSelection()); + if (mExtend != null && mExtend.getSelection()) { + refactoring.setParent(mParentStyle); + } + List<Attr> attributes = new ArrayList<Attr>(); + for (Object o : checkedElements) { + attributes.add((Attr) o); + } + refactoring.setChosenAttributes(attributes); + } + + setPageComplete(ok); + return ok; + } + + private class ArgumentLabelProvider extends StyledCellLabelProvider { + public ArgumentLabelProvider() { + } + + @Override + public void update(ViewerCell cell) { + Object element = cell.getElement(); + Attr attribute = (Attr) element; + + StyledString styledString = new StyledString(); + styledString.append(attribute.getLocalName()); + styledString.append(" = ", QUALIFIER_STYLER); + styledString.append(attribute.getValue()); + + if (mElementCount > 1) { + Integer f = mFrequencyCount.get(attribute); + String s = String.format(" (in %d/%d elements)", + f != null ? f.intValue(): 1, mElementCount); + styledString.append(s, DECORATIONS_STYLER); + } + cell.setText(styledString.toString()); + cell.setStyleRanges(styledString.getStyleRanges()); + super.update(cell); + } + } + + private class ArgumentContentProvider implements IStructuredContentProvider { + public ArgumentContentProvider() { + } + + @Override + public Object[] getElements(Object inputElement) { + if (inputElement == mRoot) { + return mAllAttributes.toArray(); + } + + return new Object[0]; + } + + @Override + public void dispose() { + } + + @Override + public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { + } + } + } +} |