/* * 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) { } } } }