blob: 08e0ed96136b9badee080d7a7ea90ade2b29e3b1 [file] [log] [blame]
/*=============================================================================#
# Copyright (c) 2007, 2021 Stephan Wahlbrink and others.
#
# This program and the accompanying materials are made available under the
# terms of the Eclipse Public License 2.0 which is available at
# https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
# which is available at https://www.apache.org/licenses/LICENSE-2.0.
#
# SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
#
# Contributors:
# Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation
#=============================================================================*/
package org.eclipse.statet.internal.r.debug.ui.preferences;
import static org.eclipse.statet.jcommons.lang.ObjectUtils.nonNullElse;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.eclipse.core.databinding.DataBindingContext;
import org.eclipse.core.databinding.ValidationStatusProvider;
import org.eclipse.core.databinding.observable.IObservable;
import org.eclipse.core.databinding.observable.Observables;
import org.eclipse.core.databinding.observable.list.IObservableList;
import org.eclipse.core.databinding.observable.list.WritableList;
import org.eclipse.core.databinding.observable.value.IObservableValue;
import org.eclipse.core.databinding.observable.value.WritableValue;
import org.eclipse.core.databinding.validation.ValidationStatus;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.databinding.swt.typed.WidgetProperties;
import org.eclipse.jface.databinding.viewers.typed.ViewerProperties;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.viewers.ArrayContentProvider;
import org.eclipse.jface.viewers.ColumnLabelProvider;
import org.eclipse.jface.viewers.ColumnWeightData;
import org.eclipse.jface.viewers.ComboViewer;
import org.eclipse.jface.viewers.IElementComparer;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.TableViewerColumn;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerComparator;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
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.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Link;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.ui.statushandlers.StatusManager;
import org.eclipse.statet.jcommons.collections.ImCollections;
import org.eclipse.statet.jcommons.lang.NonNullByDefault;
import org.eclipse.statet.ecommons.databinding.jface.DataBindingSupport;
import org.eclipse.statet.ecommons.preferences.core.Preference;
import org.eclipse.statet.ecommons.preferences.core.PreferenceAccess;
import org.eclipse.statet.ecommons.preferences.core.util.PreferenceUtils;
import org.eclipse.statet.ecommons.preferences.ui.ConfigurationBlock;
import org.eclipse.statet.ecommons.preferences.ui.ConfigurationBlockPreferencePage;
import org.eclipse.statet.ecommons.preferences.ui.ManagedConfigurationBlock;
import org.eclipse.statet.ecommons.runtime.core.StatusChangeListener;
import org.eclipse.statet.ecommons.ui.SharedMessages;
import org.eclipse.statet.ecommons.ui.components.ButtonGroup;
import org.eclipse.statet.ecommons.ui.components.DataAdapter;
import org.eclipse.statet.ecommons.ui.components.DropDownButton;
import org.eclipse.statet.ecommons.ui.util.LayoutUtils;
import org.eclipse.statet.ecommons.ui.viewers.ViewerUtils;
import org.eclipse.statet.ecommons.ui.viewers.ViewerUtils.TableComposite;
import org.eclipse.statet.internal.r.ui.help.IRUIHelpContextIds;
import org.eclipse.statet.r.core.RCore;
import org.eclipse.statet.r.core.RCorePreferenceNodes;
import org.eclipse.statet.r.core.renv.IREnvConfiguration;
import org.eclipse.statet.r.core.renv.IREnvManager;
import org.eclipse.statet.r.launching.RRunDebugPreferenceConstants;
import org.eclipse.statet.r.ui.RUI;
import org.eclipse.statet.rj.renv.core.REnv;
/**
* Preference page for R (Environment) configuration of the workbench.
*/
public class REnvPreferencePage extends ConfigurationBlockPreferencePage {
public static final String PREF_PAGE_ID= "org.eclipse.statet.r.preferencePages.REnvironmentPage"; //$NON-NLS-1$
public REnvPreferencePage() {
}
@Override
protected ConfigurationBlock createConfigurationBlock() throws CoreException {
return new REnvConfigurationBlock(null, createStatusChangedListener());
}
}
class REnvConfigurationBlock extends ManagedConfigurationBlock
implements ButtonGroup.IActions<IREnvConfiguration.WorkingCopy> {
private final static int ADD_NEW_DEFAULT= ButtonGroup.ADD_NEW;
private final static int ADD_NEW_REMOTE= ButtonGroup.ADD_NEW | (0x1 << 8);
@NonNullByDefault
private static String trimUri(final String s) {
try {
final URI uri= new URI(s);
final URI uiUri= new URI(uri.getScheme(), null,
uri.getHost(), uri.getPort(),
uri.getPath(), null, null );
return uiUri.toString();
}
catch (final URISyntaxException e) {
return s;
}
}
private TableViewer listViewer;
private ButtonGroup<IREnvConfiguration.WorkingCopy> listButtons;
private final IObservableList<IREnvConfiguration.WorkingCopy> envList= new WritableList<>();
private final IObservableValue<IREnvConfiguration.WorkingCopy> envDefault= new WritableValue<>();
private final IObservableValue<IStatus> envListStatus= new WritableValue<>();
private ComboViewer indexConsoleViewer;
private Button networkEclipseControl;
protected REnvConfigurationBlock(final IProject project, final StatusChangeListener statusListener) {
super(project, statusListener);
}
@Override
protected String getHelpContext() {
return IRUIHelpContextIds.R_ENV;
}
@Override
protected void createBlockArea(final Composite pageComposite) {
final Map<Preference<?>, String> prefs= new HashMap<>();
prefs.put(RRunDebugPreferenceConstants.PREF_RENV_CHECK_UPDATE, null);
setupPreferenceManager(prefs);
final Label label= new Label(pageComposite, SWT.LEFT);
label.setText(Messages.REnv_REnvList_label);
label.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
{ // Table area
final Composite composite= new Composite(pageComposite, SWT.NONE);
composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
composite.setLayout(LayoutUtils.newCompositeGrid(2));
final Composite table= createTable(composite);
{ final GridData gd= new GridData(SWT.FILL, SWT.FILL, true, true);
gd.heightHint= LayoutUtils.hintHeight(this.listViewer.getTable(), 12, false);
table.setLayoutData(gd);
}
this.listButtons= new ButtonGroup<>(composite, this, false);
this.listButtons.setLayoutData(new GridData(SWT.FILL, SWT.TOP, false, true));
final SelectionListener addDefaultListener= new SelectionAdapter() {
@Override
public void widgetSelected(final SelectionEvent e) {
REnvConfigurationBlock.this.listButtons.editElement(ADD_NEW_DEFAULT, null);
}
};
final DropDownButton addButton= new DropDownButton(this.listButtons);
final Menu addMenu= addButton.getDropDownMenu();
{ final MenuItem menuItem= new MenuItem(addMenu, SWT.PUSH);
menuItem.setText(Messages.REnv_Add_Local_label);
menuItem.addSelectionListener(addDefaultListener);
}
{ final MenuItem menuItem= new MenuItem(addMenu, SWT.PUSH);
menuItem.setText(Messages.REnv_Add_Remote_label);
menuItem.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(final SelectionEvent e) {
REnvConfigurationBlock.this.listButtons.editElement(ADD_NEW_REMOTE, null);
}
});
}
addButton.addSelectionListener(addDefaultListener);
addButton.setText(SharedMessages.CollectionEditing_AddItem_label + "...");
addButton.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
this.listButtons.addCopyButton(null);
this.listButtons.addEditButton(null);
this.listButtons.addDeleteButton(null);
this.listButtons.addSeparator();
this.listButtons.addDefaultButton(null);
this.listButtons.connectTo(this.listViewer, new DataAdapter.ListAdapter<IREnvConfiguration.WorkingCopy>(
this.envList, this.envDefault ) {
@Override
public boolean isDeleteAllowed(final Object element) {
final IREnvConfiguration config= (IREnvConfiguration) element;
return config.isEditable();
}
});
this.listViewer.setComparer(new IElementComparer() {
@Override
public int hashCode(final Object element) {
if (element instanceof IREnvConfiguration) {
return ((IREnvConfiguration) element).getREnv().hashCode();
}
return element.hashCode();
}
@Override
public boolean equals(final Object a, final Object b) {
if (a instanceof IREnvConfiguration && b instanceof IREnvConfiguration) {
return ((IREnvConfiguration) a).getREnv().equals(
((IREnvConfiguration) b).getREnv());
}
return a.equals(b);
}
});
this.listViewer.setInput(this.envList);
ViewerUtils.scheduleStandardSelection(this.listViewer);
}
loadValues(PreferenceUtils.getInstancePrefs());
final Composite indexOptions= createIndexOptions(pageComposite);
indexOptions.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
final Composite networkOptions= createNetworkOptions(pageComposite);
networkOptions.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
initBindings();
updateStatus();
final DataBindingContext dbc= getDataBinding().getContext();
dbc.addValidationStatusProvider(new ValidationStatusProvider() {
@Override
public IObservableValue<IStatus> getValidationStatus() {
return REnvConfigurationBlock.this.envListStatus;
}
@Override
public IObservableList<IObservable> getModels() {
return Observables.staticObservableList(dbc.getValidationRealm(),
Collections.emptyList());
}
@Override
public IObservableList<IObservable> getTargets() {
return Observables.staticObservableList(dbc.getValidationRealm(),
Collections.emptyList());
}
});
updateControls();
this.listButtons.refresh();
}
@Override
public IREnvConfiguration.WorkingCopy edit(final int command,
final IREnvConfiguration.WorkingCopy config, final Object parent) {
final boolean newConfig= ((command & ButtonGroup.ADD_ANY) != 0);
final IREnvConfiguration.WorkingCopy editConfig;
if (newConfig) {
if (config != null) { // copy
editConfig= RCore.getREnvManager().newConfiguration(config.getType());
editConfig.load(config);
}
else { // add
if (command == ADD_NEW_REMOTE) {
editConfig= RCore.getREnvManager()
.newConfiguration(IREnvConfiguration.USER_REMOTE_TYPE);
}
else {
editConfig= RCore.getREnvManager()
.newConfiguration(IREnvConfiguration.USER_LOCAL_TYPE);
}
}
}
else {
editConfig= config.createWorkingCopy();
}
if (doEdit(editConfig, newConfig)) {
if (newConfig) {
return editConfig;
}
else {
config.load(editConfig);
return config;
}
}
return null;
}
private boolean doEdit(final IREnvConfiguration.WorkingCopy config, final boolean newConfig) {
final List<IREnvConfiguration> existingConfigs= new ArrayList<>(this.envList);
if (!newConfig) {
for (final Iterator<IREnvConfiguration> iter= existingConfigs.iterator(); iter.hasNext();) {
final IREnvConfiguration existing= iter.next();
if (existing.getREnv() == config.getREnv()) {
iter.remove();
break;
}
}
}
Dialog dialog;
if (config.isLocal()) {
dialog= new LocalREnvConfigDialog(getShell(),
config, newConfig, existingConfigs);
}
else if (config.isRemote()) {
dialog= new RemoteREnvConfigDialog(getShell(),
config, newConfig, existingConfigs);
}
else {
return false;
}
return (dialog.open() == Dialog.OK);
}
@Override
public void updateState(final IStructuredSelection selection) {
REnvConfigurationBlock.this.updateStatus();
}
private Composite createTable(final Composite parent) {
final TableComposite composite= new ViewerUtils.TableComposite(parent, SWT.BORDER | SWT.MULTI | SWT.FULL_SELECTION | SWT.V_SCROLL);
this.listViewer= composite.viewer;
composite.table.setHeaderVisible(true);
composite.table.setLinesVisible(true);
{ final TableViewerColumn column= new TableViewerColumn(composite.viewer, SWT.NONE);
composite.layout.setColumnData(column.getColumn(), new ColumnWeightData(1));
column.getColumn().setText(Messages.REnv_NameColumn_name);
column.setLabelProvider(new REnvLabelProvider(this.envDefault));
}
{ final TableViewerColumn column= new TableViewerColumn(composite.viewer, SWT.NONE);
composite.layout.setColumnData(column.getColumn(), new ColumnWeightData(1));
column.getColumn().setText(Messages.REnv_LocationColumn_name);
column.setLabelProvider(new ColumnLabelProvider() {
@Override
public String getText(final Object element) {
final IREnvConfiguration config= (IREnvConfiguration) element;
if (config.getType() == IREnvConfiguration.USER_LOCAL_TYPE) {
return nonNullElse(config.getRHomeDirectory(), ""); //$NON-NLS-1$
}
if (config.getType() == IREnvConfiguration.EPLUGIN_LOCAL_TYPE) {
return "<supplied>";
}
if (config.getType() == IREnvConfiguration.USER_REMOTE_TYPE) {
final String server= config.getStateSharedServer();
return (server != null) ? trimUri(server) : ""; //$NON-NLS-1$
}
return ""; //$NON-NLS-1$
}
});
}
composite.viewer.setContentProvider(new ArrayContentProvider());
// Sorter
composite.viewer.setComparator(new ViewerComparator() {
@Override
public int compare(final Viewer viewer, final Object e1, final Object e2) {
return getComparator().compare(((IREnvConfiguration) e1).getName(), ((IREnvConfiguration) e2).getName());
}
});
return composite;
}
private Composite createIndexOptions(final Composite parent) {
final Group composite= new Group(parent, SWT.NONE);
composite.setLayout(LayoutUtils.newGroupGrid(2));
composite.setText(Messages.REnv_Index_label);
final Label label= new Label(composite, SWT.NONE);
label.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false));
label.setText(Messages.REnv_Update_Console_label);
this.indexConsoleViewer= new ComboViewer(composite, SWT.DROP_DOWN | SWT.READ_ONLY);
this.indexConsoleViewer.getControl().setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, true, false));
this.indexConsoleViewer.setLabelProvider(new LabelProvider() {
@Override
public String getText(final Object element) {
if (element.equals(RRunDebugPreferenceConstants.AUTO)) {
return Messages.REnv_Update_Console_Auto_label;
}
if (element.equals(RRunDebugPreferenceConstants.ASK)) {
return Messages.REnv_Update_Console_Ask_label;
}
if (element.equals(RRunDebugPreferenceConstants.DISABLED)) {
return Messages.REnv_Update_Console_Disabled_label;
}
return ""; //$NON-NLS-1$
}
});
this.indexConsoleViewer.setContentProvider(new ArrayContentProvider());
this.indexConsoleViewer.setInput(new String[] {
RRunDebugPreferenceConstants.AUTO,
RRunDebugPreferenceConstants.ASK,
RRunDebugPreferenceConstants.DISABLED,
});
return composite;
}
private Composite createNetworkOptions(final Composite parent) {
final Group composite= new Group(parent, SWT.NONE);
composite.setLayout(LayoutUtils.newGroupGrid(2));
composite.setText(Messages.REnv_Network_label);
{ final Composite line= new Composite(composite, SWT.NONE);
line.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 2, 1));
final GridLayout layout= LayoutUtils.newCompositeGrid(2);
layout.horizontalSpacing= 0;
line.setLayout(layout);
this.networkEclipseControl= new Button(line, SWT.CHECK);
final int idx= Messages.REnv_Network_UseEclipse_label.indexOf("<a");
this.networkEclipseControl.setText(Messages.REnv_Network_UseEclipse_label.substring(0, idx).trim());
this.networkEclipseControl.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false));
final Link link= addLinkControl(line, Messages.REnv_Network_UseEclipse_label.substring(idx));
link.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
}
return composite;
}
@Override
protected void addBindings(final DataBindingSupport db) {
db.getContext().bindValue(
ViewerProperties.singleSelection(String.class)
.observe(this.indexConsoleViewer),
createObservable(RRunDebugPreferenceConstants.PREF_RENV_CHECK_UPDATE) );
db.getContext().bindValue(
WidgetProperties.buttonSelection()
.observe(this.networkEclipseControl),
createObservable(RCorePreferenceNodes.PREF_RENV_NETWORK_USE_ECLIPSE) );
}
@Override
public boolean performOk(final int flags) {
boolean ok= super.performOk(flags);
if (this.listButtons.getDataAdapter().isDirty()) {
ok&= saveValues((flags & SAVE_STORE) != 0);
}
return ok;
}
private void updateStatus() {
this.envListStatus.setValue((this.envDefault.getValue() == null) ?
ValidationStatus.warning(Messages.REnv_warning_NoDefaultConfiguration_message) :
ValidationStatus.ok() );
}
private boolean saveValues(final boolean saveStore) {
try {
final IREnvConfiguration defaultREnv= this.envDefault.getValue();
RCore.getREnvManager().set(
ImCollections.toList(this.envList),
(defaultREnv != null) ? defaultREnv.getREnv().getId() : null );
return true;
}
catch (final CoreException e) {
StatusManager.getManager().handle(new Status(IStatus.ERROR, RUI.BUNDLE_ID,
-1, Messages.REnv_error_Saving_message, e),
StatusManager.LOG | StatusManager.SHOW);
return false;
}
}
private void loadValues(final PreferenceAccess prefs) {
this.envList.clear();
this.envDefault.setValue(null);
final IREnvManager manager= RCore.getREnvManager();
final REnv defaultEnv= manager.getDefault().resolve();
final List<IREnvConfiguration> rEnvConfigs= manager.getConfigurations();
for (final IREnvConfiguration rEnvConfig : rEnvConfigs) {
final IREnvConfiguration.WorkingCopy config= rEnvConfig.createWorkingCopy();
this.envList.add(config);
if (config.getREnv() == defaultEnv) {
this.envDefault.setValue(config);
}
}
}
}