/*
* SonarLint for Eclipse
* Copyright (C) 2015-2017 SonarSource SA
* sonarlint@sonarsource.com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonarlint.eclipse.ui.internal.properties;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javax.annotation.CheckForNull;
import org.sonarlint.eclipse.core.internal.adapter.Adapters;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.layout.TableColumnLayout;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.viewers.ColumnWeightData;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITableLabelProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.GC;
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.Link;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPreferencePage;
import org.eclipse.ui.dialogs.PreferencesUtil;
import org.eclipse.ui.dialogs.PropertyPage;
import org.sonarlint.eclipse.core.SonarLintLogger;
import org.sonarlint.eclipse.core.internal.PreferencesUtils;
import org.sonarlint.eclipse.core.internal.resources.SonarLintProjectConfiguration;
import org.sonarlint.eclipse.core.internal.resources.SonarLintProperty;
import org.sonarlint.eclipse.core.internal.utils.StringUtils;
import org.sonarlint.eclipse.core.resource.ISonarLintProject;
import org.sonarlint.eclipse.ui.internal.Messages;
import org.sonarlint.eclipse.ui.internal.SonarLintUiPlugin;
/**
* An abstract field editor that manages a list of input values.
* The editor displays a list containing the values, buttons for
* adding and removing values, and Up and Down buttons to adjust
* the order of elements in the list.
* <p>
* Subclasses must implement the <code>parseString</code>,
* <code>createList</code>, and <code>getNewInputObject</code>
* framework methods.
* </p>
*/
public class SonarLintExtraArgumentsPreferenceAndPropertyPage extends PropertyPage implements IWorkbenchPreferencePage {
private static final String VALUE = "Value";
private static final String PREFERENCE_ID = "org.sonarlint.eclipse.ui.properties.SonarExtraArgumentsPreferenceAndPropertyPage";
/**
* Label provider for templates.
*/
private static class SonarPropertiesLabelProvider extends LabelProvider implements ITableLabelProvider {
@Override
public Image getColumnImage(Object element, int columnIndex) {
return null;
}
@Override
public String getColumnText(Object element, int columnIndex) {
SonarLintProperty data = (SonarLintProperty) element;
switch (columnIndex) {
case 0:
return data.getName();
case 1:
return data.getValue();
default:
return ""; //$NON-NLS-1$
}
}
}
/**
* A content provider for the template preference page's table viewer.
*
* @since 3.0
*/
private static class SonarPropertiesContentProvider implements IStructuredContentProvider {
private List<SonarLintProperty> sonarProperties;
@Override
public Object[] getElements(Object input) {
return sonarProperties.toArray();
}
@Override
public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
sonarProperties = (List<SonarLintProperty>) newInput;
}
@Override
public void dispose() {
sonarProperties = null;
}
}
private List<SonarLintProperty> sonarProperties;
/**
* The Remove button.
*/
private Button removeButton;
/**
* The Edit button.
*/
private Button editButton;
/**
* The Up button.
*/
private Button upButton;
/**
* The Down button.
*/
private Button downButton;
private TableViewer fTableViewer;
public SonarLintExtraArgumentsPreferenceAndPropertyPage() {
setTitle(Messages.SonarPreferencePage_label_extra_args);
}
@Override
protected Control createContents(final Composite ancestor) {
loadProperties();
Composite parent = new Composite(ancestor, SWT.NONE);
GridLayout layout = new GridLayout();
layout.numColumns = 2;
layout.marginHeight = 0;
layout.marginWidth = 0;
parent.setLayout(layout);
if (!isGlobal()) {
createLinkToGlobal(ancestor, parent);
}
Composite innerParent = new Composite(parent, SWT.NONE);
GridLayout innerLayout = new GridLayout();
innerLayout.numColumns = 2;
innerLayout.marginHeight = 0;
innerLayout.marginWidth = 0;
innerParent.setLayout(innerLayout);
GridData gd = new GridData(GridData.FILL_BOTH);
gd.horizontalSpan = 2;
innerParent.setLayoutData(gd);
Composite tableComposite = new Composite(innerParent, SWT.NONE);
GridData data = new GridData(GridData.FILL_BOTH);
data.widthHint = 360;
data.heightHint = convertHeightInCharsToPixels(10);
tableComposite.setLayoutData(data);
TableColumnLayout columnLayout = new TableColumnLayout();
tableComposite.setLayout(columnLayout);
Table table = new Table(tableComposite, SWT.BORDER | SWT.MULTI | SWT.FULL_SELECTION | SWT.H_SCROLL | SWT.V_SCROLL);
table.setHeaderVisible(true);
table.setLinesVisible(true);
GC gc = new GC(getShell());
gc.setFont(JFaceResources.getDialogFont());
TableColumn propertyNameColumn = new TableColumn(table, SWT.NONE);
propertyNameColumn.setText("Name");
int minWidth = computeMinimumColumnWidth(gc, "Name");
columnLayout.setColumnData(propertyNameColumn, new ColumnWeightData(1, minWidth, true));
TableColumn propertyValueColumn = new TableColumn(table, SWT.NONE);
propertyValueColumn.setText(VALUE);
minWidth = computeMinimumColumnWidth(gc, VALUE);
columnLayout.setColumnData(propertyValueColumn, new ColumnWeightData(1, minWidth, true));
gc.dispose();
fTableViewer = new TableViewer(table);
fTableViewer.setLabelProvider(new SonarPropertiesLabelProvider());
fTableViewer.setContentProvider(new SonarPropertiesContentProvider());
fTableViewer.setComparator(null);
fTableViewer.addDoubleClickListener(e -> edit());
fTableViewer.addSelectionChangedListener(e -> updateButtons());
createButtons(innerParent);
fTableViewer.setInput(sonarProperties);
updateButtons();
Dialog.applyDialogFont(parent);
innerParent.layout();
return parent;
}
private void createButtons(Composite innerParent) {
GridLayout layout;
Composite buttons = new Composite(innerParent, SWT.NONE);
buttons.setLayoutData(new GridData(GridData.VERTICAL_ALIGN_BEGINNING));
layout = new GridLayout();
layout.marginHeight = 0;
layout.marginWidth = 0;
buttons.setLayout(layout);
Button addButton = new Button(buttons, SWT.PUSH);
addButton.setText("New...");
addButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
addButton.addListener(SWT.Selection, e -> add());
editButton = new Button(buttons, SWT.PUSH);
editButton.setText("Edit...");
editButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
editButton.addListener(SWT.Selection, e -> edit());
removeButton = new Button(buttons, SWT.PUSH);
removeButton.setText("Remove");
removeButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
removeButton.addListener(SWT.Selection, e -> remove());
upButton = new Button(buttons, SWT.PUSH);
upButton.setText("Up");
upButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
upButton.addListener(SWT.Selection, e -> upPressed());
downButton = new Button(buttons, SWT.PUSH);
downButton.setText("Down");
downButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
downButton.addListener(SWT.Selection, e -> downPressed());
}
private static void createLinkToGlobal(final Composite ancestor, Composite parent) {
Link fLink = new Link(parent, SWT.NONE);
fLink.setText("<A>Configure Workspace Settings...</A>");
fLink.setLayoutData(new GridData());
SelectionAdapter sl = new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
PreferencesUtil.createPreferenceDialogOn(ancestor.getShell(), PREFERENCE_ID, null, null).open();
}
};
fLink.addSelectionListener(sl);
}
private void edit() {
IStructuredSelection selection = (IStructuredSelection) fTableViewer.getSelection();
Object[] objects = selection.toArray();
if ((objects == null) || (objects.length != 1)) {
return;
}
SonarLintProperty data = (SonarLintProperty) selection.getFirstElement();
edit(data);
}
private void add() {
SonarLintProperty newProperty = editSonarProperty(new SonarLintProperty("", ""), false, true);
if (newProperty != null) {
sonarProperties.add(newProperty);
fTableViewer.refresh();
fTableViewer.setSelection(new StructuredSelection(newProperty));
}
}
private void remove() {
IStructuredSelection selection = (IStructuredSelection) fTableViewer.getSelection();
Iterator elements = selection.iterator();
while (elements.hasNext()) {
SonarLintProperty data = (SonarLintProperty) elements.next();
sonarProperties.remove(data);
}
fTableViewer.refresh();
}
private void edit(SonarLintProperty data) {
SonarLintProperty oldProp = data;
SonarLintProperty newProp = editSonarProperty(new SonarLintProperty(oldProp), true, false);
if (newProp != null) {
data.setValue(newProp.getValue());
fTableViewer.refresh(data);
updateButtons();
fTableViewer.setSelection(new StructuredSelection(data));
}
}
/**
* Updates the buttons.
*/
protected void updateButtons() {
IStructuredSelection selection = (IStructuredSelection) fTableViewer.getSelection();
int selectionCount = selection.size();
int itemCount = fTableViewer.getTable().getItemCount();
editButton.setEnabled(selectionCount == 1);
removeButton.setEnabled(selectionCount > 0 && selectionCount <= itemCount);
int index = sonarProperties.indexOf(selection.getFirstElement());
removeButton.setEnabled(index >= 0);
upButton.setEnabled(itemCount > 1 && index > 0);
downButton.setEnabled(itemCount > 1 && index >= 0 && index < itemCount - 1);
}
/**
* Creates the edit dialog. Subclasses may override this method to provide a
* custom dialog.
*
* @param property the property being edited
* @param edit whether this is a new property or an existing being edited
* @param isNameModifiable whether the property name may be modified
* @return the created or modified property, or <code>null</code> if the edition failed
*/
protected SonarLintProperty editSonarProperty(SonarLintProperty property, boolean edit, boolean isNameModifiable) {
EditSonarPropertyDialog dialog = new EditSonarPropertyDialog(getShell(), property, edit, isNameModifiable);
if (dialog.open() == Window.OK) {
return dialog.getSonarProperty();
}
return null;
}
private static int computeMinimumColumnWidth(GC gc, String string) {
// pad 10 to accommodate table header trimmings
return gc.stringExtent(string).x + 10;
}
/**
* Notifies that the Down button has been pressed.
*/
private void downPressed() {
swap(false);
}
/**
* Moves the currently selected item up or down.
*
* @param up <code>true</code> if the item should move up,
* and <code>false</code> if it should move down
*/
private void swap(boolean up) {
IStructuredSelection selection = (IStructuredSelection) fTableViewer.getSelection();
Object[] objects = selection.toArray();
if ((objects == null) || (objects.length != 1)) {
return;
}
SonarLintProperty data = (SonarLintProperty) selection.getFirstElement();
int index = sonarProperties.indexOf(data);
int target = up ? (index - 1) : (index + 1);
if (index >= 0) {
sonarProperties.remove(index);
sonarProperties.add(target, data);
fTableViewer.setSelection(new StructuredSelection(data));
fTableViewer.refresh();
updateButtons();
}
}
/**
* Notifies that the Up button has been pressed.
*/
private void upPressed() {
swap(true);
}
@Override
public void init(IWorkbench workbench) {
setDescription("Additional properties passed to SonarLint analyzers");
setPreferenceStore(SonarLintUiPlugin.getDefault().getPreferenceStore());
}
private void loadProperties() {
sonarProperties = new ArrayList<>();
if (isGlobal()) {
String props = getPreferenceStore().getString(PreferencesUtils.PREF_EXTRA_ARGS);
try {
String[] keyValuePairs = StringUtils.split(props, "\r\n");
for (String keyValuePair : keyValuePairs) {
String[] keyValue = StringUtils.split(keyValuePair, "=");
sonarProperties.add(new SonarLintProperty(keyValue[0], keyValue[1]));
}
} catch (Exception e) {
SonarLintLogger.get().error("Error while loading SonarLint analyzer properties" + props, e);
}
} else {
SonarLintProjectConfiguration sonarProject = getProjectConfig();
if (sonarProject != null) {
sonarProperties.addAll(sonarProject.getExtraProperties());
}
}
}
@Override
public boolean performOk() {
List<String> keyValuePairs = new ArrayList<>(sonarProperties.size());
for (SonarLintProperty prop : sonarProperties) {
keyValuePairs.add(prop.getName() + "=" + prop.getValue());
}
String props = StringUtils.joinSkipNull(keyValuePairs, "\r\n");
if (isGlobal()) {
getPreferenceStore().setValue(PreferencesUtils.PREF_EXTRA_ARGS, props);
} else {
SonarLintProjectConfiguration sonarProject = getProjectConfig();
if (sonarProject != null) {
sonarProject.setExtraProperties(sonarProperties);
sonarProject.save();
}
}
return true;
}
@Override
protected void performDefaults() {
sonarProperties.clear();
fTableViewer.refresh();
}
private ISonarLintProject getProject() {
return Adapters.adapt(getElement(), ISonarLintProject.class);
}
@CheckForNull
private SonarLintProjectConfiguration getProjectConfig() {
ISonarLintProject project = getProject();
if (project != null) {
return SonarLintProjectConfiguration.read(project.getScopeContext());
}
return null;
}
private boolean isGlobal() {
return getElement() == null;
}
}