| /*=============================================================================# |
| # 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(); |
| } |
| |
| } |