blob: 3b9093ecedb7a8fcd562cd1e57c0882ccc4da736 [file] [log] [blame]
/*=============================================================================#
# Copyright (c) 2007, 2019 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.ui;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import com.ibm.icu.text.Collator;
import org.eclipse.core.databinding.Binding;
import org.eclipse.core.databinding.DataBindingContext;
import org.eclipse.core.databinding.observable.Diffs;
import org.eclipse.core.databinding.observable.Realm;
import org.eclipse.core.databinding.observable.list.IObservableList;
import org.eclipse.core.databinding.observable.value.AbstractObservableValue;
import org.eclipse.core.databinding.observable.value.IObservableValue;
import org.eclipse.core.databinding.validation.IValidator;
import org.eclipse.core.databinding.validation.ValidationStatus;
import org.eclipse.core.runtime.IStatus;
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.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Text;
import org.eclipse.statet.jcommons.collections.CopyOnWriteIdentityListSet;
import org.eclipse.statet.jcommons.collections.ImCollections;
import org.eclipse.statet.jcommons.collections.ImIdentitySet;
import org.eclipse.statet.ecommons.preferences.core.PreferenceAccess;
import org.eclipse.statet.ecommons.preferences.core.PreferenceSetService.ChangeEvent;
import org.eclipse.statet.ecommons.preferences.core.util.PreferenceUtils;
import org.eclipse.statet.ecommons.preferences.ui.PreferenceSetUIListener;
import org.eclipse.statet.ecommons.ui.util.LayoutUtils;
import org.eclipse.statet.internal.r.debug.ui.preferences.REnvPreferencePage;
import org.eclipse.statet.r.core.RCore;
import org.eclipse.statet.r.core.renv.IREnvManager;
import org.eclipse.statet.rj.renv.core.REnv;
import org.eclipse.statet.rj.renv.core.REnvConfiguration;
import org.eclipse.statet.rj.renv.core.REnvUtils;
/**
* Composite to choose a configured R Environment.
*/
public class REnvSelectionComposite extends Composite {
private static final ImIdentitySet<String> PREF_QUALIFIERS= ImCollections.newIdentitySet(
IREnvManager.PREF_QUALIFIER );
private static final Comparator<REnv> RENV_COMPARATOR= new Comparator<REnv>() {
@Override
public int compare(final REnv o1, final REnv o2) {
return Collator.getInstance().compare(o1.getName(), o2.getName());
}
};
public static interface ChangeListener {
public void settingChanged(REnvSelectionComposite source, String oldValue, String newValue,
REnv newREnv);
}
private class CompositeObservable extends AbstractObservableValue<String> implements ChangeListener {
public CompositeObservable(final Realm realm) {
super(realm);
REnvSelectionComposite.this.addChangeListener(CompositeObservable.this);
}
@Override
public Object getValueType() {
return String.class;
}
@Override
protected void doSetValue(final String value) {
setEncodedSetting(value);
}
@Override
protected String doGetValue() {
return getEncodedSetting();
}
@Override
public void settingChanged(final REnvSelectionComposite source, final String oldValue,
final String newValue, final REnv newREnv) {
fireValueChange(Diffs.createValueDiff(oldValue, newValue));
}
public REnvSelectionComposite getComposite() {
return REnvSelectionComposite.this;
}
}
private class ChooseREnvValidator implements IValidator<Object> {
@Override
public IStatus validate(final Object dummy) {
if (REnvSelectionComposite.this.invalidPreference) {
return ValidationStatus.error(RUIMessages.ChooseREnv_error_InvalidPreferences_message);
}
if (REnvSelectionComposite.this.currentREnv == null) {
if (REnvSelectionComposite.this.enableNone) {
return ValidationStatus.ok();
}
return ValidationStatus.error(RUIMessages.ChooseREnv_error_IncompleteSelection_message);
}
final REnv rEnv= REnvSelectionComposite.this.currentREnv.resolve();
if (rEnv == null
|| (REnvSelectionComposite.this.validREnvs != null && !REnvSelectionComposite.this.validREnvs.contains(rEnv))
|| rEnv.get(REnvConfiguration.class) == null) {
return ValidationStatus.error(RUIMessages.ChooseREnv_error_InvalidSelection_message);
}
return ValidationStatus.ok();
}
}
private final PreferenceAccess prefAccess;
private final boolean enableNone;
private boolean invalidPreference;
private List<REnv> validREnvs;
private REnv currentREnv;
private String currentEncoded;
private REnv currentSpecified;
private final CopyOnWriteIdentityListSet<ChangeListener> listeners= new CopyOnWriteIdentityListSet<>();
private DataBindingContext bindindContext;
private Binding bindings;
private Button noneButton;
private Button workbenchDefaultButton;
private Text workbenchLabel;
private Button specificButton;
private Combo specificCombo;
private Button configurationButton;
public REnvSelectionComposite(final Composite parent) {
this(parent, false);
}
public REnvSelectionComposite(final Composite parent, final boolean enableNone) {
super(parent, SWT.NONE);
this.enableNone= enableNone;
this.invalidPreference= true;
createControls();
this.workbenchDefaultButton.setSelection(true);
this.prefAccess= PreferenceUtils.getInstancePrefs();
initPreferences();
updateState(true, false);
}
private void initPreferences() {
new PreferenceSetUIListener(this.prefAccess, this.specificCombo) {
@Override
protected void handlePreferenceChanged(final ChangeEvent event) {
if (event.contains(IREnvManager.PREF_QUALIFIER)) {
loadREnvironments();
}
}
}.subscribe(PREF_QUALIFIERS);
loadREnvironments();
}
private void createControls() {
final Composite container= this;
container.setLayout(LayoutUtils.newCompositeGrid(3));
if (this.enableNone) {
this.noneButton= new Button(container, SWT.RADIO);
this.noneButton.setText(RUIMessages.ChooseREnv_None_label);
this.noneButton.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 3, 1));
}
this.workbenchDefaultButton= new Button(container, SWT.RADIO);
this.workbenchDefaultButton.setText(RUIMessages.ChooseREnv_WorkbenchDefault_label);
this.workbenchDefaultButton.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false));
this.workbenchLabel= new Text(container, SWT.BORDER | SWT.LEFT | SWT.SINGLE | SWT.READ_ONLY);
this.workbenchLabel.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
LayoutUtils.addGDDummy(container);
this.specificButton= new Button(container, SWT.RADIO);
this.specificButton.setText(RUIMessages.ChooseREnv_Selected_label);
this.specificButton.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false));
this.specificCombo= new Combo(container, SWT.DROP_DOWN | SWT.READ_ONLY);
this.specificCombo.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false));
this.configurationButton= new Button(container, SWT.PUSH);
this.configurationButton.setText(RUIMessages.ChooseREnv_Configure_label);
this.configurationButton.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false));
if (this.enableNone) {
this.noneButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(final SelectionEvent e) {
if (REnvSelectionComposite.this.noneButton.getSelection()) {
REnvSelectionComposite.this.currentREnv= null;
updateState(false, false);
}
}
});
}
this.workbenchDefaultButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(final SelectionEvent e) {
if (REnvSelectionComposite.this.workbenchDefaultButton.getSelection()) {
REnvSelectionComposite.this.currentREnv= RCore.getREnvManager().getDefault();
updateState(false, false);
}
}
});
this.specificButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(final SelectionEvent e) {
if (REnvSelectionComposite.this.specificButton.getSelection()) {
REnvSelectionComposite.this.currentREnv= REnvSelectionComposite.this.currentSpecified;
updateState(false, false);
}
}
});
this.specificCombo.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(final SelectionEvent event) {
final String name= getSpecifiedName();
if (name != null) {
REnvSelectionComposite.this.currentREnv= RCore.getREnvManager().get(null, name);
updateState(false, false);
}
}
});
this.configurationButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(final SelectionEvent e) {
org.eclipse.ui.dialogs.PreferencesUtil.createPreferenceDialogOn(getShell(),
REnvPreferencePage.PREF_PAGE_ID, new String[] { REnvPreferencePage.PREF_PAGE_ID },
null).open();
}
});
}
private void loadREnvironments() {
this.invalidPreference= true;
final IREnvManager manager= RCore.getREnvManager();
manager.getReadLock().lock();
try {
// Workbench default
final REnv defaultEnv= manager.getDefault();
final List<? extends REnvConfiguration> list= manager.getConfigurations();
this.validREnvs= getValidREnvs(list);
Collections.sort(this.validREnvs, RENV_COMPARATOR);
final String[] validNames= new String[this.validREnvs.size()];
for (int i= 0; i < validNames.length; i++) {
validNames[i]= this.validREnvs.get(i).getName();
}
this.workbenchLabel.setText(defaultEnv.getName());
if (!list.isEmpty()) {
this.invalidPreference= false;
}
// Specifics
this.specificCombo.setItems(validNames);
if (this.currentSpecified != null) {
final boolean current= (this.currentREnv == this.currentSpecified);
this.currentSpecified= manager.get(this.currentSpecified.getId(), this.currentSpecified.getName());
if (current) {
this.currentREnv= this.currentSpecified;
}
}
}
finally {
manager.getReadLock().unlock();
updateState(false, true);
}
}
protected List<REnv> getValidREnvs(final List<? extends REnvConfiguration> configurations) {
final List<REnv> list= new ArrayList<>(configurations.size());
for (final REnvConfiguration rEnvConfig : configurations) {
if (isValid(rEnvConfig)) {
list.add(rEnvConfig.getREnv());
}
}
return list;
}
protected boolean isValid(final REnvConfiguration rEnvConfig) {
return (!rEnvConfig.getREnv().isDeleted());
}
public void setSetting(final REnv rEnv) {
this.currentREnv= rEnv;
updateState(true, false);
}
public String getEncodedSetting() {
return this.currentEncoded;
}
public void setEncodedSetting(final String encodedSetting) {
setSetting(REnvUtils.decode(encodedSetting, RCore.getREnvManager()));
}
private String getSpecifiedName() {
final int idx= this.specificCombo.getSelectionIndex();
if (idx >= 0) {
return this.specificCombo.getItem(idx);
}
return null;
}
public REnv getSelection() {
return this.currentREnv;
}
private void updateState(final boolean updateSelection, final boolean force) {
final boolean isWorkbench= (this.currentREnv != null
&& this.currentREnv.getId().equals(RCore.DEFAULT_WORKBENCH_ENV_ID) );
final boolean isSpecific= (this.currentREnv != null && !isWorkbench);
if (updateSelection) {
if (this.noneButton != null) {
this.noneButton.setSelection(!isWorkbench && !isSpecific);
}
this.workbenchDefaultButton.setSelection(isWorkbench);
this.specificButton.setSelection(isSpecific);
}
this.workbenchLabel.setEnabled(this.workbenchDefaultButton.getSelection());
this.specificCombo.setEnabled(this.specificButton.getSelection());
if (isSpecific) {
this.currentSpecified= this.currentREnv;
}
if (this.currentSpecified != null) {
this.specificCombo.select(this.specificCombo.indexOf(this.currentSpecified.getName()));
}
else {
this.specificCombo.deselectAll();
}
final String oldEncoded= this.currentEncoded;
this.currentEncoded= REnvUtils.encode(this.currentREnv);
if (!((this.currentEncoded != null) ? this.currentEncoded.equals(oldEncoded) : (null == oldEncoded))) {
for (final ChangeListener listener : this.listeners.toList()) {
listener.settingChanged(this, oldEncoded, this.currentEncoded, this.currentREnv);
}
}
else if (force) {
checkBindings();
if (this.bindings != null) {
this.bindings.validateTargetToModel();
}
}
}
private void checkBindings() {
if (this.bindindContext != null) {
final IObservableList<Binding> bindings= this.bindindContext.getBindings();
for (final Binding binding : bindings) {
if (binding.getTarget() instanceof CompositeObservable) {
if (((CompositeObservable) binding.getTarget()).getComposite() == this) {
this.bindings= binding;
this.bindindContext= null;
return;
}
}
}
}
}
public void addChangeListener(final ChangeListener listener) {
this.listeners.add(listener);
}
public void removeChangeListener(final ChangeListener listener) {
this.listeners.remove(listener);
}
/**
* Return a new Observable for the encoded setting of selected REnv.
* (So type is String)
*/
public IObservableValue<String> createObservable(final Realm realm) {
return new CompositeObservable(realm);
}
public IValidator<Object> createValidator(final DataBindingContext context) {
this.bindindContext= context;
return new ChooseREnvValidator();
}
}