/*******************************************************************************
* Copyright (c) 2009, 2015 Alena Laskavaia 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:
* Alena Laskavaia - initial API and implementation
* Alex Ruiz (Google)
* Sergey Prigogin (Google)
*******************************************************************************/
package org.eclipse.cdt.codan.internal.ui.preferences;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import org.eclipse.cdt.codan.core.CodanCorePlugin;
import org.eclipse.cdt.codan.core.CodanRuntime;
import org.eclipse.cdt.codan.core.model.CheckerLaunchMode;
import org.eclipse.cdt.codan.core.model.IChecker;
import org.eclipse.cdt.codan.core.model.ICheckersRegistry;
import org.eclipse.cdt.codan.core.model.ICodanBuilder;
import org.eclipse.cdt.codan.core.model.ICodanProblemMarker;
import org.eclipse.cdt.codan.core.model.IProblem;
import org.eclipse.cdt.codan.core.model.IProblemProfile;
import org.eclipse.cdt.codan.internal.core.CheckersRegistry;
import org.eclipse.cdt.codan.internal.ui.CodanUIActivator;
import org.eclipse.cdt.codan.internal.ui.CodanUIMessages;
import org.eclipse.cdt.codan.internal.ui.dialogs.CustomizeProblemDialog;
import org.eclipse.cdt.core.dom.ast.IASTTranslationUnit;
import org.eclipse.cdt.core.model.CoreModelUtil;
import org.eclipse.cdt.core.model.ILanguage;
import org.eclipse.cdt.core.model.ITranslationUnit;
import org.eclipse.cdt.internal.core.model.ASTCache.ASTRunnable;
import org.eclipse.cdt.internal.corext.util.CModelUtil;
import org.eclipse.cdt.internal.ui.editor.ASTProvider;
import org.eclipse.cdt.ui.ICEditor;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceRuleFactory;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.core.runtime.preferences.InstanceScope;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.viewers.DoubleClickEvent;
import org.eclipse.jface.viewers.IDoubleClickListener;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.ITreeSelection;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
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.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IEditorReference;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchPreferencePage;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.preferences.ScopedPreferenceStore;
/**
* This class represents a preference page that is contributed to the Preferences dialog.
* By subclassing {@code FieldEditorPreferencePage}, we can use built-in field support in
* JFace to create a page that is both small and knows how to save, restore and apply its
* values.
* <p>
* This page is used to modify preferences only. They are stored in the preference store that
* belongs to the main plug-in class. That way, preferences can be accessed directly via
* the preference store.
* </p>
*/
public class CodanPreferencePage extends FieldEditorOverlayPage implements IWorkbenchPreferencePage {
private static final String EMPTY_STRING = ""; //$NON-NLS-1$
private IProblemProfile profile;
private ISelectionChangedListener problemSelectionListener;
private ArrayList<IProblem> selectedProblems;
private Button infoButton;
private ProblemsTreeEditor checkedTreeEditor;
public CodanPreferencePage() {
super(GRID);
setPreferenceStore(new ScopedPreferenceStore(InstanceScope.INSTANCE, CodanCorePlugin.PLUGIN_ID));
problemSelectionListener = new ISelectionChangedListener() {
@Override
public void selectionChanged(SelectionChangedEvent event) {
if (infoButton != null && event.getSelection() instanceof ITreeSelection) {
ITreeSelection s = (ITreeSelection) event.getSelection();
ArrayList<IProblem> list = new ArrayList<IProblem>();
for (Iterator<?> iterator = s.iterator(); iterator.hasNext();) {
Object o = iterator.next();
if (o instanceof IProblem) {
list.add((IProblem) o);
}
}
setSelectedProblems(list);
}
}
};
}
@Override
protected String getPageId() {
return "org.eclipse.cdt.codan.internal.ui.preferences.CodanPreferencePage"; //$NON-NLS-1$
}
/**
* Creates the field editors. Field editors are abstractions of the common
* GUI blocks needed to
* manipulate various types of preferences. Each field editor knows how to
* save and restore
* its own value.
*/
@Override
public void createFieldEditors() {
checkedTreeEditor = new ProblemsTreeEditor(getFieldEditorParent(), profile);
addField(checkedTreeEditor);
checkedTreeEditor.getTreeViewer().addSelectionChangedListener(problemSelectionListener);
checkedTreeEditor.getTreeViewer().addDoubleClickListener(new IDoubleClickListener() {
@Override
public void doubleClick(DoubleClickEvent event) {
openCustomizeDialog();
}
});
GridData layoutData = new GridData(GridData.FILL, GridData.FILL, true, true);
layoutData.heightHint = 200;
checkedTreeEditor.getTreeViewer().getControl().setLayoutData(layoutData);
}
@Override
protected Control createContents(Composite parent) {
if (isPropertyPage()) {
profile = getRegistry().getResourceProfileWorkingCopy((IResource) getElement());
} else {
profile = getRegistry().getWorkspaceProfile();
}
Composite comp = (Composite) super.createContents(parent);
createInfoControl(parent);
return comp;
}
private void createInfoControl(Composite comp) {
Composite info = new Composite(comp, SWT.NONE);
info.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
GridLayout layout = new GridLayout(1, false);
layout.marginWidth = 0;
info.setLayout(layout);
infoButton = new Button(info, SWT.PUSH);
infoButton.setLayoutData(GridDataFactory.swtDefaults().align(SWT.END, SWT.BEGINNING).create());
infoButton.setText(CodanUIMessages.CodanPreferencePage_Customize);
infoButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
openCustomizeDialog();
}
});
restoreWidgetValues();
}
protected void setSelectedProblems(ArrayList<IProblem> list) {
this.selectedProblems = list;
updateProblemInfo();
}
protected ICheckersRegistry getRegistry() {
return CodanRuntime.getInstance().getCheckersRegistry();
}
@Override
public boolean performOk() {
saveWidgetValues();
IResource resource = (IResource) getElement();
getRegistry().updateProfile(resource, null);
boolean success = super.performOk();
if (success) {
if (resource == null) {
resource = ResourcesPlugin.getWorkspace().getRoot();
}
asynchronouslyUpdateMarkers(resource);
}
return success;
}
private void saveWidgetValues() {
String id = !hasSelectedProblems() ? EMPTY_STRING : selectedProblems.get(0).getId();
getDialogSettings().put(getWidgetId(), id);
}
private void restoreWidgetValues() {
String id = getDialogSettings().get(getWidgetId());
if (id != null && !id.isEmpty() && checkedTreeEditor != null) {
IProblem problem = profile.findProblem(id);
if (problem != null) {
checkedTreeEditor.getTreeViewer().setSelection(new StructuredSelection(problem), true);
}
} else {
setSelectedProblems(null);
}
updateProblemInfo();
}
private IDialogSettings getDialogSettings() {
return CodanUIActivator.getDefault().getDialogSettings();
}
protected String getWidgetId() {
return getPageId() + ".selection"; //$NON-NLS-1$
}
private void updateProblemInfo() {
infoButton.setEnabled(hasSelectedProblems());
}
@Override
public void init(IWorkbench workbench) {
}
protected void openCustomizeDialog() {
if (!hasSelectedProblems()) {
return;
}
IProblem[] selected = selectedProblems.toArray(new IProblem[selectedProblems.size()]);
CustomizeProblemDialog dialog = new CustomizeProblemDialog(getShell(), selected, (IResource) getElement());
dialog.open();
checkedTreeEditor.getTreeViewer().refresh(true);
}
private boolean hasSelectedProblems() {
return selectedProblems != null && !selectedProblems.isEmpty();
}
private static void asynchronouslyUpdateMarkers(final IResource resource) {
final Set<IFile> filesToUpdate = new HashSet<IFile>();
final IWorkbench workbench = PlatformUI.getWorkbench();
IWorkbenchWindow active = workbench.getActiveWorkbenchWindow();
final IWorkbenchPage page = active.getActivePage();
// Get the files open C/C++ editors.
for (IEditorReference partRef : page.getEditorReferences()) {
IEditorPart editor = partRef.getEditor(false);
if (editor instanceof ICEditor) {
IFile file = editor.getEditorInput().getAdapter(IFile.class);
if (file != null && resource.getFullPath().isPrefixOf(file.getFullPath())) {
filesToUpdate.add(file);
}
}
}
Job job = new Job(CodanUIMessages.CodanPreferencePage_Update_markers) {
@Override
protected IStatus run(IProgressMonitor monitor) {
final SubMonitor submonitor = SubMonitor.convert(monitor, 1 + 2 * filesToUpdate.size());
removeMarkersForDisabledProblems(resource, submonitor.newChild(1));
if (filesToUpdate.isEmpty())
return Status.OK_STATUS;
// Run checkers on the currently open files to update the problem markers.
for (final IFile file : filesToUpdate) {
ITranslationUnit tu = CoreModelUtil.findTranslationUnit(file);
if (tu != null) {
tu = CModelUtil.toWorkingCopy(tu);
ASTProvider.getASTProvider().runOnAST(
tu, ASTProvider.WAIT_ACTIVE_ONLY, submonitor.newChild(1),
new ASTRunnable() {
@Override
public IStatus runOnAST(ILanguage lang, IASTTranslationUnit ast) {
ICodanBuilder builder = CodanRuntime.getInstance().getBuilder();
if (ast != null) {
builder.processResource(file, submonitor.newChild(1), CheckerLaunchMode.RUN_AS_YOU_TYPE, ast);
} else {
builder.processResource(file, submonitor.newChild(1), CheckerLaunchMode.RUN_ON_FILE_OPEN, null);
}
return Status.OK_STATUS;
}
});
}
}
return Status.OK_STATUS;
}
};
IResourceRuleFactory ruleFactory = ResourcesPlugin.getWorkspace().getRuleFactory();
job.setRule(ruleFactory.markerRule(resource));
job.setSystem(true);
job.schedule();
}
private static void removeMarkersForDisabledProblems(IResource resource, IProgressMonitor monitor) {
CheckersRegistry chegistry = CheckersRegistry.getInstance();
Set<String> markerTypes = new HashSet<String>();
for (IChecker checker : chegistry) {
Collection<IProblem> problems = chegistry.getRefProblems(checker);
for (IProblem problem : problems) {
markerTypes.add(problem.getMarkerType());
}
}
try {
removeMarkersForDisabledProblems(chegistry, markerTypes, resource, monitor);
} catch (CoreException e) {
CodanUIActivator.log(e);
}
}
private static void removeMarkersForDisabledProblems(CheckersRegistry chegistry,
Set<String> markerTypes, IResource resource, IProgressMonitor monitor) throws CoreException {
if (!resource.isAccessible()) {
return;
}
IResource[] children = null;
if (resource instanceof IContainer) {
children = ((IContainer) resource).members();
}
int numChildren = children == null ? 0 : children.length;
int childWeight = 10;
SubMonitor progress = SubMonitor.convert(monitor, 1 + numChildren * childWeight);
IProblemProfile resourceProfile = null;
for (String markerType : markerTypes) {
IMarker[] markers = resource.findMarkers(markerType, false, IResource.DEPTH_ZERO);
for (IMarker marker : markers) {
String problemId = (String) marker.getAttribute(ICodanProblemMarker.ID);
if (resourceProfile == null) {
resourceProfile = chegistry.getResourceProfile(resource);
}
IProblem problem = resourceProfile.findProblem(problemId);
if (problem != null && !problem.isEnabled()) {
marker.delete();
}
}
}
progress.worked(1);
if (children != null) {
for (IResource child : children) {
if (monitor.isCanceled())
return;
removeMarkersForDisabledProblems(chegistry, markerTypes, child,
progress.newChild(childWeight));
}
}
}
}