blob: ae745603e42f0bea2008a31c23785243c42780ec [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2004, 2017 TimeSys Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* TimeSys Corporation - Initial implementation
*******************************************************************************/
package org.eclipse.cdt.internal.ui.preferences;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import org.eclipse.cdt.core.model.CoreModel;
import org.eclipse.cdt.internal.ui.util.Messages;
import org.eclipse.cdt.internal.ui.util.SWTUtil;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.ProjectScope;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.ListenerList;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.content.IContentType;
import org.eclipse.core.runtime.content.IContentTypeManager;
import org.eclipse.core.runtime.content.IContentTypeSettings;
import org.eclipse.core.runtime.preferences.IScopeContext;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.layout.PixelConverter;
import org.eclipse.jface.viewers.ColumnWeightData;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.jface.viewers.ILabelProviderListener;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITableLabelProvider;
import org.eclipse.jface.viewers.TableLayout;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerComparator;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.SWT;
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.Table;
import org.eclipse.swt.widgets.TableColumn;
/*
* Preference block that encapsulates the controls used
* for displaying/editing CDT file type associations
*/
public class CFileTypesPreferenceBlock {
private static final int COL_PATTERN = 0;
private static final int COL_DESCRIPTION = 1;
private static final int COL_STATUS = 2;
private ArrayList<CFileTypeAssociation> fAddAssoc;
private ArrayList<CFileTypeAssociation> fRemoveAssoc;
private boolean fDirty = false;
private IProject fInput;
private IContentType[] fContentTypes;
private TableViewer fAssocViewer;
private Button fBtnNew;
private Button fBtnRemove;
private class AssocComparator extends ViewerComparator {
@Override
public int category(Object element) {
if (element instanceof CFileTypeAssociation) {
CFileTypeAssociation assoc = (CFileTypeAssociation) element;
if (assoc.isExtSpec()) {
return 10;
}
return 20;
}
return 30;
}
}
private class AssocContentProvider implements IStructuredContentProvider {
CFileTypeAssociation[] assocs;
@Override
public Object[] getElements(Object inputElement) {
return assocs;
}
@Override
public void dispose() {
assocs = null;
}
@Override
public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
if (newInput instanceof CFileTypeAssociation[]) {
assocs = (CFileTypeAssociation[]) newInput;
}
}
}
private class AssocLabelProvider implements ILabelProvider, ITableLabelProvider {
private ListenerList<ILabelProviderListener> listeners = new ListenerList<>();
@Override
public Image getColumnImage(Object element, int columnIndex) {
if (element instanceof CFileTypeAssociation) {
if (COL_PATTERN == columnIndex) {
return null;
}
}
return null;
}
@Override
public String getColumnText(Object element, int columnIndex) {
if (element instanceof CFileTypeAssociation) {
CFileTypeAssociation assoc = (CFileTypeAssociation) element;
switch (columnIndex) {
case COL_PATTERN:
return assoc.getPattern();
case COL_DESCRIPTION:
return assoc.getDescription();
case COL_STATUS:
if (assoc.isUserDefined()) {
return PreferencesMessages.CFileTypesPreferencePage_userDefined;
} else if (assoc.isPredefined()) {
return PreferencesMessages.CFileTypesPreferencePage_preDefined;
}
return ""; //$NON-NLS-1$
}
}
return element.toString();
}
@Override
public void addListener(ILabelProviderListener listener) {
listeners.add(listener);
}
@Override
public void dispose() {
listeners.clear();
listeners = null;
}
@Override
public boolean isLabelProperty(Object element, String property) {
return false;
}
@Override
public void removeListener(ILabelProviderListener listener) {
listeners.remove(listener);
}
@Override
public Image getImage(Object element) {
return getColumnImage(element, 0);
}
@Override
public String getText(Object element) {
return getColumnText(element, 0);
}
}
public CFileTypesPreferenceBlock() {
this(null);
}
public CFileTypesPreferenceBlock(IProject input) {
fAddAssoc = new ArrayList<>();
fRemoveAssoc = new ArrayList<>();
fInput = input;
setDirty(false);
}
public Control createControl(Composite parent) {
Composite control = new Composite(parent, SWT.NONE);
GridLayout controlLayout = new GridLayout(2, false);
controlLayout.marginHeight = 0;
controlLayout.marginWidth = 0;
control.setLayout(controlLayout);
control.setLayoutData(new GridData(GridData.FILL_BOTH | GridData.GRAB_VERTICAL));
// Create the table viewer for file associations
Composite tablePane = new Composite(control, SWT.NONE);
GridLayout tablePaneLayout = new GridLayout();
GridData gridData = new GridData(GridData.FILL_BOTH);
tablePaneLayout.marginHeight = 0;
tablePaneLayout.marginWidth = 0;
tablePane.setLayout(tablePaneLayout);
tablePane.setLayoutData(gridData);
Table table = new Table(tablePane, SWT.MULTI | SWT.V_SCROLL | SWT.BORDER | SWT.FULL_SELECTION);
TableLayout tblLayout = new TableLayout();
TableColumn col = null;
gridData = new GridData(GridData.FILL_BOTH);
gridData.grabExcessHorizontalSpace = true;
gridData.grabExcessVerticalSpace = true;
gridData.heightHint = SWTUtil.getTableHeightHint(table, 15);
gridData.widthHint = new PixelConverter(parent).convertWidthInCharsToPixels(60);
tblLayout.addColumnData(new ColumnWeightData(20));
tblLayout.addColumnData(new ColumnWeightData(60));
tblLayout.addColumnData(new ColumnWeightData(20));
table.setLayout(tblLayout);
table.setLayoutData(gridData);
table.setHeaderVisible(true);
table.setLinesVisible(true);
col = new TableColumn(table, SWT.LEFT);
col.setText(PreferencesMessages.CFileTypesPreferencePage_colTitlePattern);
col = new TableColumn(table, SWT.LEFT);
col.setText(PreferencesMessages.CFileTypesPreferencePage_colTitleDescription);
col = new TableColumn(table, SWT.LEFT);
col.setText(PreferencesMessages.CFileTypesPreferencePage_colTitleStatus);
// Create the button pane
Composite buttonPane = new Composite(control, SWT.NONE);
GridLayout buttonPaneLayout = new GridLayout();
buttonPaneLayout.marginHeight = 0;
buttonPaneLayout.marginWidth = 0;
buttonPane.setLayout(buttonPaneLayout);
buttonPane.setLayoutData(new GridData(GridData.VERTICAL_ALIGN_BEGINNING));
// New button
fBtnNew = new Button(buttonPane, SWT.PUSH);
fBtnNew.setText(PreferencesMessages.CFileTypesPreferenceBlock_New___);
gridData = new GridData(GridData.FILL_HORIZONTAL);
gridData.widthHint = SWTUtil.getButtonWidthHint(fBtnNew);
fBtnNew.setLayoutData(gridData);
fBtnNew.addListener(SWT.Selection, e -> handleAdd());
// Remove button
fBtnRemove = new Button(buttonPane, SWT.PUSH);
fBtnRemove.setText(PreferencesMessages.CFileTypesPreferenceBlock_Remove);
gridData = new GridData(GridData.FILL_HORIZONTAL);
gridData.widthHint = SWTUtil.getButtonWidthHint(fBtnRemove);
fBtnRemove.setLayoutData(gridData);
fBtnRemove.addListener(SWT.Selection, e -> handleRemove());
// Hook up the viewer
fAssocViewer = new TableViewer(table);
fAssocViewer.setComparator(new AssocComparator());
fAssocViewer.setContentProvider(new AssocContentProvider());
fAssocViewer.setLabelProvider(new AssocLabelProvider());
fAssocViewer.setInput(getCFileTypeAssociations());
fAssocViewer.addSelectionChangedListener(event -> handleSelectionChanged());
handleSelectionChanged();
return control;
}
public void setEnabled(boolean enabled) {
fAssocViewer.getTable().setEnabled(enabled);
fBtnNew.setEnabled(enabled);
fBtnRemove.setEnabled(enabled);
setDirty(enabled);
}
public void setInput(IProject input) {
fAddAssoc.clear();
fRemoveAssoc.clear();
fInput = input;
if (null != fAssocViewer) {
fAssocViewer.setInput(getCFileTypeAssociations());
}
setDirty(true);
}
private void setDirty(boolean dirty) {
fDirty = dirty;
}
public boolean performOk() {
boolean changed = fDirty;
if (fDirty) {
CFileTypeAssociation[] add = fAddAssoc.toArray(new CFileTypeAssociation[fAddAssoc.size()]);
CFileTypeAssociation[] rem = fRemoveAssoc.toArray(new CFileTypeAssociation[fRemoveAssoc.size()]);
changed = add.length > 0 || rem.length > 0;
adjustAssociations(add, rem);
fAddAssoc.clear();
fRemoveAssoc.clear();
setDirty(false);
}
return changed;
}
private CFileTypeAssociation[] getCFileTypeAssociations() {
ArrayList<CFileTypeAssociation> list = new ArrayList<>();
if (fInput == null) {
fillWithUserDefinedCFileTypeAssociations(list);
fillWithPredefinedCFileTypeAssociations(list);
} else {
fillWithProjectCFileTypeAssociations(list, fInput);
}
CFileTypeAssociation[] assocs = new CFileTypeAssociation[list.size()];
list.toArray(assocs);
return assocs;
}
protected void adjustAssociations(CFileTypeAssociation[] add, CFileTypeAssociation[] rem) {
IScopeContext context = null;
if (fInput != null) {
context = new ProjectScope(fInput);
}
removeAssociations(rem, context);
addAssociations(add, context);
}
final protected void addAssociations(CFileTypeAssociation[] add, IScopeContext context) {
for (int i = 0; i < add.length; ++i) {
CFileTypeAssociation assoc = add[i];
String spec = assoc.getSpec();
IContentType contentType = assoc.getContentType();
int type = IContentType.FILE_NAME_SPEC;
if (assoc.isExtSpec()) {
type = IContentType.FILE_EXTENSION_SPEC;
}
addAssociation(context, contentType, spec, type);
}
}
protected void addAssociation(IScopeContext context, IContentType contentType, String spec, int type) {
try {
IContentTypeSettings settings = contentType.getSettings(context);
settings.addFileSpec(spec, type);
} catch (CoreException e) {
// ignore ??
}
}
protected void removeAssociations(CFileTypeAssociation[] rem, IScopeContext context) {
for (int i = 0; i < rem.length; ++i) {
CFileTypeAssociation assoc = rem[i];
IContentType contentType = assoc.getContentType();
String spec = assoc.getSpec();
int type = IContentType.FILE_NAME_SPEC;
if (assoc.isExtSpec()) {
type = IContentType.FILE_EXTENSION_SPEC;
}
removeAssociation(context, contentType, spec, type);
}
}
protected void removeAssociation(IScopeContext context, IContentType contentType, String spec, int type) {
try {
IContentTypeSettings settings = contentType.getSettings(context);
settings.removeFileSpec(spec, type);
} catch (CoreException e) {
// ignore ??
}
}
public IContentType[] getRegistedContentTypes() {
if (fContentTypes == null) {
String[] ids = CoreModel.getRegistedContentTypeIds();
IContentTypeManager manager = Platform.getContentTypeManager();
IContentType[] ctypes = new IContentType[ids.length];
for (int i = 0; i < ids.length; i++) {
ctypes[i] = manager.getContentType(ids[i]);
}
fContentTypes = ctypes;
}
return fContentTypes;
}
private void fillWithUserDefinedCFileTypeAssociations(ArrayList<CFileTypeAssociation> list) {
IContentType[] ctypes = getRegistedContentTypes();
fillWithCFileTypeAssociations(ctypes, null, IContentType.IGNORE_PRE_DEFINED | IContentType.FILE_EXTENSION_SPEC,
list);
fillWithCFileTypeAssociations(ctypes, null, IContentType.IGNORE_PRE_DEFINED | IContentType.FILE_NAME_SPEC,
list);
}
private void fillWithPredefinedCFileTypeAssociations(ArrayList<CFileTypeAssociation> list) {
IContentType[] ctypes = getRegistedContentTypes();
fillWithCFileTypeAssociations(ctypes, null, IContentType.IGNORE_USER_DEFINED | IContentType.FILE_EXTENSION_SPEC,
list);
fillWithCFileTypeAssociations(ctypes, null, IContentType.IGNORE_USER_DEFINED | IContentType.FILE_NAME_SPEC,
list);
}
private void fillWithProjectCFileTypeAssociations(ArrayList<CFileTypeAssociation> list, IProject project) {
IContentType[] ctypes = getRegistedContentTypes();
IScopeContext context = new ProjectScope(project);
fillWithCFileTypeAssociations(ctypes, context,
IContentType.IGNORE_PRE_DEFINED | IContentType.FILE_EXTENSION_SPEC, list);
fillWithCFileTypeAssociations(ctypes, context, IContentType.IGNORE_PRE_DEFINED | IContentType.FILE_NAME_SPEC,
list);
}
private void fillWithCFileTypeAssociations(IContentType[] ctypes, IScopeContext context, int type,
ArrayList<CFileTypeAssociation> list) {
for (IContentType ctype : ctypes) {
try {
IContentTypeSettings setting = ctype.getSettings(context);
String[] specs = setting.getFileSpecs(type);
for (String spec : specs) {
CFileTypeAssociation assoc = new CFileTypeAssociation(spec, type, ctype);
list.add(assoc);
}
} catch (CoreException e) {
// skip over it.
}
}
}
private CFileTypeAssociation createAssociation(String pattern, IContentType contentType) {
int type = IContentType.FILE_NAME_SPEC;
if (pattern.startsWith("*.")) { //$NON-NLS-1$
pattern = pattern.substring(2);
type = IContentType.FILE_EXTENSION_SPEC;
}
return new CFileTypeAssociation(pattern, type | IContentType.IGNORE_PRE_DEFINED, contentType);
}
protected void handleSelectionChanged() {
IStructuredSelection sel = getSelection();
if (sel.isEmpty()) {
fBtnRemove.setEnabled(false);
} else {
boolean enabled = true;
List<?> elements = sel.toList();
for (Object element : elements) {
CFileTypeAssociation assoc = (CFileTypeAssociation) element;
if (assoc.isPredefined())
enabled = false;
}
fBtnRemove.setEnabled(enabled);
}
}
final protected void handleAdd() {
CFileTypeAssociation assoc = null;
CFileTypeDialog dlg = new CFileTypeDialog(fBtnNew.getParent().getShell());
if (Window.OK == dlg.open()) {
assoc = createAssociation(dlg.getPattern(), dlg.getContentType());
if (handleAdd(assoc)) {
fAssocViewer.add(assoc);
setDirty(true);
}
}
}
private boolean handleAdd(CFileTypeAssociation assoc) {
// assoc is marked to be added.
if (containsIgnoreCaseOfSpec(fAddAssoc, assoc)) {
reportDuplicateAssociation(assoc);
return false;
}
// assoc exists, but is marked to be removed.
if (containsIgnoreCaseOfSpec(fRemoveAssoc, assoc)) {
if (!fRemoveAssoc.remove(assoc)) {
fAddAssoc.add(assoc);
}
return true;
}
// analyze current settings
IContentTypeSettings settings;
if (fInput == null) {
settings = assoc.getContentType();
} else {
try {
settings = assoc.getContentType().getSettings(new ProjectScope(fInput));
} catch (CoreException e) {
ErrorDialog.openError(fBtnNew.getParent().getShell(),
PreferencesMessages.CFileTypesPreferenceBlock_addAssociationError_title, null, e.getStatus());
return false;
}
}
String newSpec = assoc.getSpec();
String[] specs = settings.getFileSpecs(assoc.getFileSpecType());
for (String spec : specs) {
if (spec.equalsIgnoreCase(newSpec)) {
reportDuplicateAssociation(assoc);
return false;
}
}
fAddAssoc.add(assoc);
return true;
}
private boolean containsIgnoreCaseOfSpec(Collection<CFileTypeAssociation> collection, CFileTypeAssociation assoc) {
for (CFileTypeAssociation existing : collection) {
if (assoc.equalsIgnoreCaseOfSpec(existing)) {
return true;
}
}
return false;
}
private void reportDuplicateAssociation(CFileTypeAssociation assoc) {
MessageDialog.openError(fBtnNew.getParent().getShell(),
PreferencesMessages.CFileTypesPreferenceBlock_addAssociationError_title,
Messages.format(PreferencesMessages.CFileTypesPreferenceBlock_addAssociationErrorMessage,
assoc.getPattern(), assoc.getContentType().getName()));
}
final protected void handleRemove() {
IStructuredSelection sel = getSelection();
if ((null != sel) && (!sel.isEmpty())) {
for (Iterator<?> iter = sel.iterator(); iter.hasNext();) {
CFileTypeAssociation assoc = (CFileTypeAssociation) iter.next();
handleRemove(assoc);
fAssocViewer.remove(assoc);
setDirty(true);
}
}
}
private void handleRemove(CFileTypeAssociation assoc) {
if (!fAddAssoc.remove(assoc)) {
fRemoveAssoc.add(assoc);
}
}
private IStructuredSelection getSelection() {
return (IStructuredSelection) fAssocViewer.getSelection();
}
}