blob: 187452d217b2bd0692ed4312a419246d51466722 [file] [log] [blame]
/*
* 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) {
}
}
}
}