/*******************************************************************************
* Copyright (c) 2013 Google, Inc and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Sergey Prigogin (Google) - initial API and implementation
*******************************************************************************/
package org.eclipse.cdt.internal.ui.preferences;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
import org.eclipse.core.filesystem.EFS;
import org.eclipse.core.filesystem.IFileStore;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.layout.PixelConverter;
import org.eclipse.jface.viewers.IFontProvider;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ITableLabelProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.window.Window;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.XMLMemento;
import com.ibm.icu.text.Collator;
import org.eclipse.cdt.internal.corext.codemanipulation.IncludeInfo;
import org.eclipse.cdt.internal.ui.ICHelpContextIds;
import org.eclipse.cdt.internal.ui.dialogs.ResizableStatusDialog;
import org.eclipse.cdt.internal.ui.dialogs.StatusInfo;
import org.eclipse.cdt.internal.ui.refactoring.includes.HeaderSubstitutionMap;
import org.eclipse.cdt.internal.ui.refactoring.includes.IncludeMap;
import org.eclipse.cdt.internal.ui.wizards.dialogfields.ComboDialogField;
import org.eclipse.cdt.internal.ui.wizards.dialogfields.DialogField;
import org.eclipse.cdt.internal.ui.wizards.dialogfields.IDialogFieldListener;
import org.eclipse.cdt.internal.ui.wizards.dialogfields.IListAdapter;
import org.eclipse.cdt.internal.ui.wizards.dialogfields.LayoutUtil;
import org.eclipse.cdt.internal.ui.wizards.dialogfields.ListDialogField;
import org.eclipse.cdt.internal.ui.wizards.dialogfields.Separator;
import org.eclipse.cdt.internal.ui.wizards.dialogfields.StringDialogField;
/**
* Dialog for editing a header file substitution map.
*/
public class HeaderSubstitutionMapEditDialog extends ResizableStatusDialog {
private static final String UTF_8 = "UTF-8"; //$NON-NLS-1$
private static final String TAG_HEADER_SUBSTITUTION_MAP = "header_substitution_map"; //$NON-NLS-1$
private static final Collator COLLATOR = Collator.getInstance();
private static final Comparator<HeaderSubstitutionRule> SOURCE_COMPARATOR =
new Comparator<HeaderSubstitutionRule>() {
@Override
public int compare(HeaderSubstitutionRule r1, HeaderSubstitutionRule r2) {
return COLLATOR.compare(r1.getSource(), r2.getSource());
}
};
private class HeaderSubstitutionListField extends ListDialogField<HeaderSubstitutionRule> {
HeaderSubstitutionListField(IListAdapter<HeaderSubstitutionRule> adapter, String[] buttons) {
super(adapter, buttons, new HeaderSubstitutionLabelProvider());
}
@Override
protected boolean getManagedButtonState(ISelection sel, int index) {
if (index == IDX_REMOVE) {
return !sel.isEmpty();
} else if (index == IDX_UP) {
return !sel.isEmpty() && canMoveUp();
} else if (index == IDX_DOWN) {
return !sel.isEmpty() && canMoveDown();
}
return true;
}
@Override
protected boolean managedButtonPressed(int index) {
if (index == IDX_REMOVE) {
remove();
} else if (index == IDX_UP) {
up();
} else if (index == IDX_DOWN) {
down();
} else {
return false;
}
return true;
}
@Override
protected boolean canMoveUp() {
if (!isOkToUse(fTableControl))
return false;
int[] indc= fTable.getTable().getSelectionIndices();
for (int i= 0; i < indc.length; i++) {
int index = indc[i];
if (index == 0 ||
SOURCE_COMPARATOR.compare(fElements.get(index), fElements.get(index - 1)) != 0) {
return false;
}
}
return true;
}
@Override
protected boolean canMoveDown() {
if (!isOkToUse(fTableControl))
return false;
int k= fElements.size() - 1;
int[] indc= fTable.getTable().getSelectionIndices();
for (int i= 0; i < indc.length; i++) {
int index = indc[i];
if (index == k ||
SOURCE_COMPARATOR.compare(fElements.get(index), fElements.get(index + 1)) != 0) {
return false;
}
}
return true;
}
}
private class HeaderSubstitutionLabelProvider extends LabelProvider implements ITableLabelProvider, IFontProvider {
public HeaderSubstitutionLabelProvider() {
}
@Override
public Image getImage(Object element) {
return null;
}
@Override
public String getText(Object element) {
return getColumnText(element, 0);
}
@Override
public Image getColumnImage(Object element, int columnIndex) {
return null;
}
@Override
public String getColumnText(Object element, int columnIndex) {
HeaderSubstitutionRule substitution = (HeaderSubstitutionRule) element;
return columnIndex == 0 ? substitution.getSource() : substitution.getTarget();
}
@Override
public Font getFont(Object element) {
return null;
}
}
public class ListAdapter implements IListAdapter<HeaderSubstitutionRule>, IDialogFieldListener {
private boolean canEdit(List<HeaderSubstitutionRule> selectedElements) {
return selectedElements.size() == 1;
}
@Override
public void customButtonPressed(ListDialogField<HeaderSubstitutionRule> field, int index) {
onButtonPressed(field, index);
}
@Override
public void selectionChanged(ListDialogField<HeaderSubstitutionRule> field) {
List<HeaderSubstitutionRule> selectedElements = field.getSelectedElements();
field.enableButton(IDX_EDIT, canEdit(selectedElements));
}
@Override
public void doubleClicked(ListDialogField<HeaderSubstitutionRule> field) {
if (canEdit(field.getSelectedElements())) {
onButtonPressed(field, IDX_EDIT);
}
}
@Override
public void dialogFieldChanged(DialogField field) {
updateButtonState();
}
}
private static abstract class ButtonSelectionListener implements SelectionListener {
@Override
public void widgetDefaultSelected(SelectionEvent e) {
}
}
private static final int IDX_ADD = 0;
private static final int IDX_EDIT = 1;
private static final int IDX_REMOVE = 2;
private static final int IDX_UP = 3;
private static final int IDX_DOWN = 4;
private final StringDialogField fNameField;
private final ComboDialogField fAppliesToField;
private final HeaderSubstitutionListField fUnconditionalSubstitutionsField;
private final HeaderSubstitutionListField fOptionalSubstitutionsField;
private final Set<String> fExistingNames;
private final boolean fNewMap;
public HeaderSubstitutionMapEditDialog(Shell parent, HeaderSubstitutionMap map,
List<HeaderSubstitutionMap> existingEntries) {
super(parent);
fExistingNames = new HashSet<String>();
for (HeaderSubstitutionMap exising : existingEntries) {
if (!exising.equals(map)) {
fExistingNames.add(exising.getName());
}
}
if (map == null) {
fNewMap = true;
setTitle(PreferencesMessages.HeaderSubstitutionMapEditDialog_new_title);
} else {
fNewMap = false;
setTitle(PreferencesMessages.HeaderSubstitutionMapEditDialog_edit_title);
}
ListAdapter adapter = new ListAdapter();
fNameField = new StringDialogField();
fNameField.setLabelText(PreferencesMessages.HeaderSubstitutionMapEditDialog_name);
fNameField.setDialogFieldListener(adapter);
String[] items = new String[] {
PreferencesMessages.HeaderSubstitutionMapEditDialog_c_and_cpp,
PreferencesMessages.HeaderSubstitutionMapEditDialog_cpp_only,
};
fAppliesToField = new ComboDialogField(SWT.READ_ONLY);
fAppliesToField.setLabelText(PreferencesMessages.HeaderSubstitutionMapEditDialog_applies_to);
fAppliesToField.setItems(items);
String[] buttons = new String[] {
PreferencesMessages.HeaderSubstitutionMapEditDialog_add_button,
PreferencesMessages.HeaderSubstitutionMapEditDialog_edit_button,
PreferencesMessages.HeaderSubstitutionMapEditDialog_remove_button,
};
fUnconditionalSubstitutionsField = new HeaderSubstitutionListField(adapter, buttons);
fUnconditionalSubstitutionsField.setLabelText(PreferencesMessages.HeaderSubstitutionMapEditDialog_required_substitution);
fUnconditionalSubstitutionsField.setDialogFieldListener(adapter);
final String[] columnsHeaders = new String[] {
PreferencesMessages.HeaderSubstitutionMapEditDialog_header,
PreferencesMessages.HeaderSubstitutionMapEditDialog_replacement,
};
fUnconditionalSubstitutionsField.setTableColumns(new ListDialogField.ColumnsDescription(columnsHeaders, true));
buttons = new String[] {
PreferencesMessages.HeaderSubstitutionMapEditDialog_add_button2,
PreferencesMessages.HeaderSubstitutionMapEditDialog_edit_button2,
PreferencesMessages.HeaderSubstitutionMapEditDialog_remove_button2,
PreferencesMessages.HeaderSubstitutionMapEditDialog_up_button,
PreferencesMessages.HeaderSubstitutionMapEditDialog_down_button,
};
fOptionalSubstitutionsField = new HeaderSubstitutionListField(adapter, buttons);
fOptionalSubstitutionsField.setLabelText(PreferencesMessages.HeaderSubstitutionMapEditDialog_optional_substitution);
fOptionalSubstitutionsField.setDialogFieldListener(adapter);
fOptionalSubstitutionsField.enableButton(IDX_EDIT, false);
fOptionalSubstitutionsField.setTableColumns(new ListDialogField.ColumnsDescription(columnsHeaders, true));
updateFromMap(map);
adapter.selectionChanged(fUnconditionalSubstitutionsField);
adapter.selectionChanged(fOptionalSubstitutionsField);
}
private void updateFromMap(HeaderSubstitutionMap map) {
fNameField.setText(map != null ? map.getName() : createUniqueName());
fAppliesToField.selectItem(map != null && map.isCppOnly() ? 1 : 0);
if (map != null) {
List<HeaderSubstitutionRule> substitutionRules = getSubstitutionRules(map.getUnconditionalSubstitutionMap());
fUnconditionalSubstitutionsField.setElements(substitutionRules);
substitutionRules = getSubstitutionRules(map.getOptionalSubstitutionMap());
fOptionalSubstitutionsField.setElements(substitutionRules);
}
}
private String createUniqueName() {
for (int i = 1; ; i++) {
String name = NLS.bind(PreferencesMessages.HeaderSubstitutionMapEditDialog_default_map_name, i);
if (!fExistingNames.contains(name))
return name;
}
}
private List<HeaderSubstitutionRule> getSubstitutionRules(IncludeMap map) {
ArrayList<HeaderSubstitutionRule> result = new ArrayList<HeaderSubstitutionRule>();
for (Entry<IncludeInfo, List<IncludeInfo>> entry : map.getMap().entrySet()) {
String source = stripQuotes(entry.getKey().toString());
for (IncludeInfo target : entry.getValue()) {
boolean unconditional = map.isUnconditionalSubstitution();
HeaderSubstitutionRule rule =
new HeaderSubstitutionRule(source, stripQuotes(target.toString()), unconditional);
result.add(rule);
}
}
Collections.sort(result, SOURCE_COMPARATOR);
return result;
}
private String stripQuotes(String str) {
if (str.length() > 2 && str.charAt(0) == '"' && str.charAt(str.length() - 1) == '"')
return str.substring(1, str.length() - 1);
return str;
}
public HeaderSubstitutionMap getResult() {
HeaderSubstitutionMap map = createEmptyMap();
for (HeaderSubstitutionRule substitution : fUnconditionalSubstitutionsField.getElements()) {
map.addMapping(substitution.getSource(), substitution.getTarget(), true);
}
for (HeaderSubstitutionRule substitution : fOptionalSubstitutionsField.getElements()) {
map.addMapping(substitution.getSource(), substitution.getTarget(), false);
}
return map;
}
private HeaderSubstitutionMap createEmptyMap() {
HeaderSubstitutionMap map = new HeaderSubstitutionMap(fAppliesToField.getSelectionIndex() != 0);
map.setName(fNameField.getText().trim());
return map;
}
@Override
protected Control createDialogArea(Composite parent) {
Composite composite = (Composite) super.createDialogArea(parent);
PixelConverter conv = new PixelConverter(composite);
Composite inner = new Composite(composite, SWT.NONE);
inner.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
GridLayout layout = new GridLayout();
layout.marginHeight = 0;
layout.marginWidth = 0;
layout.numColumns = 4;
inner.setLayout(layout);
fNameField.doFillIntoGrid(inner, 3);
if (fNewMap)
fNameField.getTextControl(null).selectAll();
Button button = new Button(inner, SWT.PUSH);
button.setText(PreferencesMessages.HeaderSubstitutionMapEditDialog_import_button);
button.setLayoutData(new GridData(GridData.FILL, GridData.CENTER, false, false));
button.addSelectionListener(new ButtonSelectionListener() {
@Override
public void widgetSelected(SelectionEvent e) {
importFromFile();
}
});
fAppliesToField.doFillIntoGrid(inner, 3);
button = new Button(inner, SWT.PUSH);
button.setText(PreferencesMessages.HeaderSubstitutionMapEditDialog_export_button);
button.setLayoutData(new GridData(GridData.FILL, GridData.CENTER, false, false));
button.addSelectionListener(new ButtonSelectionListener() {
@Override
public void widgetSelected(SelectionEvent e) {
exportToFile();
}
});
Separator separator = new Separator(SWT.NONE);
separator.doFillIntoGrid(inner, 4, conv.convertHeightInCharsToPixels(1));
int minHeight = convertHeightInCharsToPixels(12);
fUnconditionalSubstitutionsField.doFillIntoGrid(inner, 4);
LayoutUtil.setHeightHint(fUnconditionalSubstitutionsField.getListControl(null), minHeight);
fOptionalSubstitutionsField.doFillIntoGrid(inner, 4);
LayoutUtil.setHeightHint(fOptionalSubstitutionsField.getListControl(null), minHeight);
applyDialogFont(composite);
return composite;
}
private void importFromFile() {
FileDialog dialog= new FileDialog(getShell(), SWT.OPEN);
dialog.setText(PreferencesMessages.HeaderSubstitutionMapEditDialog_import_title);
// TODO(sprigogin): Add import from .imp files
// (see http://code.google.com/p/include-what-you-use/wiki/IWYUMappings)
dialog.setFilterExtensions(new String[] { "*.xml" }); //$NON-NLS-1$
String path= dialog.open();
if (path == null)
return;
try {
IFileStore fileStore = EFS.getLocalFileSystem().getStore(Path.fromOSString(path));
InputStream stream = fileStore.openInputStream(EFS.NONE, null);
InputStreamReader reader = new InputStreamReader(new BufferedInputStream(stream), UTF_8);
try {
HeaderSubstitutionMap map = HeaderSubstitutionMap.fromSerializedMemento(reader);
updateFromMap(map);
} finally {
try {
reader.close();
} catch (IOException e) {
}
}
} catch (IOException e) {
String title= PreferencesMessages.HeaderSubstitutionMapEditDialog_import_title;
String message= e.getLocalizedMessage();
if (message != null) {
message= NLS.bind(PreferencesMessages.HeaderSubstitutionMapEditDialog_error_parse_message, message);
} else {
message= NLS.bind(PreferencesMessages.HeaderSubstitutionMapEditDialog_error_read_message, path);
}
MessageDialog.openError(getShell(), title, message);
} catch (CoreException e) {
MessageDialog.openError(getShell(),
PreferencesMessages.HeaderSubstitutionMapEditDialog_import_title,
e.getLocalizedMessage());
}
updateButtonState();
}
private void exportToFile() {
HeaderSubstitutionMap map = getResult();
FileDialog dialog= new FileDialog(getShell(), SWT.SAVE);
dialog.setText(PreferencesMessages.HeaderSubstitutionMapEditDialog_export_title);
dialog.setFilterExtensions(new String[] { "*.xml" }); //$NON-NLS-1$
dialog.setFileName(map.getName() + ".xml"); //$NON-NLS-1$
dialog.setOverwrite(true);
String path= dialog.open();
if (path == null)
return;
try {
IFileStore fileStore = EFS.getLocalFileSystem().getStore(Path.fromOSString(path));
OutputStream stream = fileStore.openOutputStream(EFS.OVERWRITE, null);
XMLMemento memento = XMLMemento.createWriteRoot(TAG_HEADER_SUBSTITUTION_MAP);
map.saveToMemento(memento);
Writer writer = new OutputStreamWriter(new BufferedOutputStream(stream), UTF_8);
try {
memento.save(writer);
} finally {
try {
writer.close();
} catch (IOException e) {
}
}
} catch (IOException e) {
MessageDialog.openError(getShell(),
PreferencesMessages.HeaderSubstitutionMapEditDialog_export_title,
PreferencesMessages.HeaderSubstitutionMapEditDialog_error_write_message);
} catch (CoreException e) {
MessageDialog.openError(getShell(),
PreferencesMessages.HeaderSubstitutionMapEditDialog_export_title,
e.getLocalizedMessage());
}
}
@Override
protected void configureShell(Shell newShell) {
super.configureShell(newShell);
PlatformUI.getWorkbench().getHelpSystem().setHelp(newShell, ICHelpContextIds.HEADER_SUBSTITUTION_MAP_EDIT_DIALOG);
}
private void onButtonPressed(ListDialogField<HeaderSubstitutionRule> field, int buttonId) {
HeaderSubstitutionRule oldRule = null;
if (buttonId == IDX_ADD) {
oldRule = new HeaderSubstitutionRule("", "", field == fUnconditionalSubstitutionsField); //$NON-NLS-1$ //$NON-NLS-2$
} else {
oldRule = field.getSelectedElements().get(0);
}
switch (buttonId) {
case IDX_ADD:
case IDX_EDIT:
HeaderSubstitutionEditDialog dialog = new HeaderSubstitutionEditDialog(getShell(), oldRule);
if (dialog.open() != Window.OK)
break;
HeaderSubstitutionRule newRule = dialog.getResult();
ListDialogField<HeaderSubstitutionRule> newField = newRule.isUnconditionalSubstitution() ?
fUnconditionalSubstitutionsField : fOptionalSubstitutionsField;
if (buttonId == IDX_ADD) {
newField.addElement(newRule);
} else {
if (newField == field) {
field.replaceElement(oldRule, newRule);
} else {
field.removeElement(oldRule);
newField.addElement(newRule);
}
}
// Restore order.
List<HeaderSubstitutionRule> elements = newField.getElements();
Collections.sort(elements, SOURCE_COMPARATOR);
newField.setElements(elements);
// There can be no more than one unconditional substitution for any header file.
// The unconditional and optional substitutions are mutually exclusive.
for (HeaderSubstitutionRule rule : fUnconditionalSubstitutionsField.getElements()) {
if (rule != newRule && rule.getSource().equals(newRule.getSource())) {
fUnconditionalSubstitutionsField.removeElement(rule);
}
}
if (newRule.isUnconditionalSubstitution()) {
List<HeaderSubstitutionRule> rulesToDelete = null;
for (HeaderSubstitutionRule rule : fOptionalSubstitutionsField.getElements()) {
if (rule.getSource().equals(newRule.getSource())) {
if (rulesToDelete == null)
rulesToDelete = new ArrayList<HeaderSubstitutionRule>();
rulesToDelete.add(rule);
}
}
if (rulesToDelete != null)
fOptionalSubstitutionsField.removeElements(rulesToDelete);
}
break;
}
updateButtonState();
}
private void updateButtonState() {
IStatus status = StatusInfo.OK_STATUS;
String name = fNameField.getText().trim();
if (name.isEmpty()) {
status = new StatusInfo(IStatus.WARNING, PreferencesMessages.HeaderSubstitutionMapEditDialog_enter_name);
} else if (fExistingNames.contains(name)) {
status = new StatusInfo(IStatus.WARNING, PreferencesMessages.HeaderSubstitutionMapEditDialog_duplicate_name);
} else if (fUnconditionalSubstitutionsField.getElements().isEmpty() &&
fOptionalSubstitutionsField.getElements().isEmpty()) {
status = new StatusInfo(IStatus.WARNING, PreferencesMessages.HeaderSubstitutionMapEditDialog_map_is_empty);
}
updateStatus(status);
}
@Override
protected void updateButtonsEnableState(IStatus status) {
// OK button is disabled unless the status is OK.
super.updateButtonsEnableState(status.isOK() ? status : new StatusInfo(IStatus.ERROR, null));
}
}