blob: f133a1660618d9da45e588b1b98825af8a1ffcea [file] [log] [blame]
/*=============================================================================#
# Copyright (c) 2012, 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.pkgmanager;
import static org.eclipse.statet.jcommons.lang.ObjectUtils.NonNull_Object_TYPE;
import static org.eclipse.statet.jcommons.lang.ObjectUtils.NonNull_String_TYPE;
import static org.eclipse.statet.jcommons.lang.ObjectUtils.Nullable_Object_TYPE;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.core.databinding.DataBindingContext;
import org.eclipse.core.databinding.UpdateValueStrategy;
import org.eclipse.core.databinding.beans.typed.PojoProperties;
import org.eclipse.core.databinding.observable.list.IObservableList;
import org.eclipse.core.databinding.observable.list.WritableList;
import org.eclipse.core.runtime.CoreException;
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.ColumnWeightData;
import org.eclipse.jface.viewers.ComboViewer;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TableViewerColumn;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
import org.eclipse.statet.jcommons.collections.ImCollections;
import org.eclipse.statet.jcommons.collections.ImList;
import org.eclipse.statet.jcommons.lang.NonNullByDefault;
import org.eclipse.statet.jcommons.lang.Nullable;
import org.eclipse.statet.ecommons.databinding.NotEmptyValidator;
import org.eclipse.statet.ecommons.databinding.URLValidator;
import org.eclipse.statet.ecommons.databinding.core.conversion.ClassTypedConverter;
import org.eclipse.statet.ecommons.databinding.jface.DataBindingSupport;
import org.eclipse.statet.ecommons.preferences.core.Preference;
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.components.ButtonGroup;
import org.eclipse.statet.ecommons.ui.components.ButtonGroup.IActions;
import org.eclipse.statet.ecommons.ui.dialogs.ExtStatusDialog;
import org.eclipse.statet.ecommons.ui.util.LayoutUtils;
import org.eclipse.statet.ecommons.ui.viewers.ComparatorViewerComparator;
import org.eclipse.statet.ecommons.ui.viewers.ViewerUtils;
import org.eclipse.statet.ecommons.ui.viewers.ViewerUtils.TableComposite;
import org.eclipse.statet.r.core.pkgmanager.IRPkgManager;
import org.eclipse.statet.r.core.pkgmanager.RRepo;
import org.eclipse.statet.rj.renv.core.RPkgType;
@NonNullByDefault
public class RRepoPreferencePage extends ConfigurationBlockPreferencePage {
public RRepoPreferencePage() {
}
@Override
protected ConfigurationBlock createConfigurationBlock() throws CoreException {
return new RRepoConfigurationBlock(createStatusChangedListener());
}
}
@NonNullByDefault
class EditRepoDialog extends ExtStatusDialog {
private static final String DEFAULT_TYPE= "Default"; //$NON-NLS-1$
@SuppressWarnings("null")
private static final Class<@Nullable RPkgType> Nullable_RPkgType_TYPE= RPkgType.class;
private final RRepo repo;
private final boolean isNew;
private Text nameControl;
private Text urlControl;
private ComboViewer typeControl;
public EditRepoDialog(final Shell parent, final RRepo repo, final boolean isNew) {
super(parent, (isNew) ? WITH_DATABINDING_CONTEXT :
(WITH_DATABINDING_CONTEXT | SHOW_INITIAL_STATUS));
this.repo= repo;
this.isNew= isNew;
setTitle(isNew ? "Add Repository" : "Edit Repository");
}
@Override
protected Control createDialogArea(final Composite parent) {
final Composite area= new Composite(parent, SWT.NONE);
area.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
area.setLayout(LayoutUtils.newDialogGrid(2));
{ final Label label= new Label(area, SWT.NONE);
label.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false));
label.setText("Name:");
}
{ final Text text= new Text(area, SWT.BORDER);
text.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
this.nameControl= text;
}
{ final Label label= new Label(area, SWT.NONE);
label.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false));
label.setText("URL:");
}
{ final Text text= new Text(area, SWT.BORDER);
final GridData gd= new GridData(SWT.FILL, SWT.CENTER, true, false);
gd.widthHint= LayoutUtils.hintWidth(this.nameControl, 60);
text.setLayoutData(gd);
this.urlControl= text;
}
{ final Label label= new Label(area, SWT.NONE);
label.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false));
label.setText("Type:");
}
{ final ComboViewer viewer= new ComboViewer(area, SWT.READ_ONLY | SWT.BORDER | SWT.DROP_DOWN);
final GridData gd= new GridData(SWT.LEFT, SWT.CENTER, true, false);
gd.widthHint= LayoutUtils.hintWidth(viewer.getCombo(), 10);
viewer.getCombo().setLayoutData(gd);
viewer.setContentProvider(ArrayContentProvider.getInstance());
viewer.setLabelProvider(new LabelProvider() {
@Override
public String getText(final Object element) {
if (element instanceof RPkgType) {
return ((RPkgType) element).getLabel();
}
return super.getText(element);
}
});
viewer.setInput(new Object[] {
DEFAULT_TYPE, RPkgType.SOURCE, RPkgType.BINARY
});
viewer.setSelection(new StructuredSelection(DEFAULT_TYPE));
this.typeControl= viewer;
}
LayoutUtils.addSmallFiller(area, true);
applyDialogFont(area);
return area;
}
@Override
protected void addBindings(final DataBindingSupport databinding) {
final DataBindingContext dbc= databinding.getContext();
dbc.bindValue(
WidgetProperties.text(SWT.Modify)
.observe(this.nameControl),
PojoProperties.value(RRepo.class, "name", NonNull_String_TYPE) //$NON-NLS-1$
.observe(this.repo),
null,
null );
dbc.bindValue(
WidgetProperties.text(SWT.Modify)
.observe(this.urlControl),
PojoProperties.value(RRepo.class, "URL", NonNull_String_TYPE) //$NON-NLS-1$
.observe(this.repo),
new UpdateValueStrategy<String, String>()
.setAfterGetValidator(
new NotEmptyValidator("URL", new URLValidator("URL")) ),
null );
dbc.bindValue(
ViewerProperties.singleSelection(Object.class)
.observe(this.typeControl),
PojoProperties.value(RRepo.class, "pkgType", Nullable_RPkgType_TYPE) //$NON-NLS-1$
.observe(this.repo),
new UpdateValueStrategy<@Nullable Object, @Nullable RPkgType>()
.setConverter(ClassTypedConverter.create(Nullable_Object_TYPE, Nullable_RPkgType_TYPE,
(final @Nullable Object fromObject) ->
(fromObject != DEFAULT_TYPE) ? (RPkgType)fromObject : null )),
new UpdateValueStrategy<@Nullable RPkgType, @Nullable Object>()
.setConverter(ClassTypedConverter.create(Nullable_RPkgType_TYPE, NonNull_Object_TYPE,
(final @Nullable RPkgType fromObject) ->
(fromObject != null) ? fromObject : DEFAULT_TYPE )));
}
}
class RRepoConfigurationBlock extends ManagedConfigurationBlock implements IActions<RRepo> {
private static final int R_SIZE= 3;
private static final int REPO= 0;
private static final int CRAN= 1;
private static final int BIOC= 2;
private static final ImList<Preference<List<RRepo>>> PREFS= ImCollections.newList(
IRPkgManager.CUSTOM_REPO_PREF,
IRPkgManager.CUSTOM_CRAN_MIRROR_PREF,
IRPkgManager.CUSTOM_BIOC_MIRROR_PREF );
private static final Comparator<RRepo> COMPARATOR= new Comparator<RRepo>() {
@Override
public int compare(final RRepo o1, final RRepo o2) {
final int diff= o1.getName().compareTo(o2.getName());
if (diff != 0) {
return diff;
}
return o1.getId().compareTo(o2.getId());
}
};
private final TableComposite[] tables= new TableComposite[R_SIZE];
@SuppressWarnings("unchecked")
private final ButtonGroup<RRepo>[] buttonsGroups= new ButtonGroup[R_SIZE];
@SuppressWarnings("unchecked")
private final IObservableList<RRepo>[] lists= new IObservableList[R_SIZE];
private final Set<String> ids= new HashSet<>();
public RRepoConfigurationBlock(final StatusChangeListener statusListener) {
super(null, "R Custom Repositories", statusListener);
}
@Override
protected void createBlockArea(final Composite pageComposite) {
final Map<Preference<?>, String> prefs= new HashMap<>();
prefs.put(IRPkgManager.CUSTOM_REPO_PREF, null);
prefs.put(IRPkgManager.CUSTOM_CRAN_MIRROR_PREF, null);
prefs.put(IRPkgManager.CUSTOM_BIOC_MIRROR_PREF, null);
setupPreferenceManager(prefs);
addLinkHeader(pageComposite, "Additional custom repositories for R packages. " +
"There is no need to add the default R repositories or mirrors.");
final Composite composite= new Composite(pageComposite, SWT.NONE);
composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
composite.setLayout(LayoutUtils.newCompositeGrid(2));
createTable(composite, REPO, "R&epositories");
createTable(composite, CRAN, "CR&AN Mirrors");
createTable(composite, BIOC, "&Bioconductor Mirrors");
initBindings();
updateControls();
}
@Override
protected void addBindings(final DataBindingSupport db) {
for (int i= 0; i < R_SIZE; i++) {
this.lists[i]= new WritableList<>(db.getRealm(), new ArrayList<RRepo>(), RRepo.class);
this.tables[i].viewer.setContentProvider(new ArrayContentProvider());
this.tables[i].viewer.setInput(this.lists[i]);
this.buttonsGroups[i].connectTo(this.tables[i].viewer, this.lists[i], null);
}
}
private void createTable(final Composite parent, final int r, final String s) {
{ final Label label= new Label(parent, SWT.NONE);
label.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 2, 1));
label.setText(s+':');
}
{ final TableComposite table= new TableComposite(parent, SWT.BORDER | SWT.MULTI | SWT.FULL_SELECTION);
// table.table.setHeaderVisible(true);
{ final GridData gd= new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1);
gd.heightHint= LayoutUtils.hintHeight(table.table, (r == REPO) ? 9 : 3);
table.setLayoutData(gd);
}
{ final TableViewerColumn column= table.addColumn("Repository", SWT.LEFT, new ColumnWeightData(100));
column.setLabelProvider(new RRepoLabelProvider());
}
// { final TableViewerColumn column= table.addColumn("Name", SWT.LEFT, new ColumnWeightData(40));
// column.setLabelProvider(new RepoLabelProvider());
// }
// { final TableViewerColumn column= table.addColumn("URL", SWT.LEFT, new ColumnWeightData(60));
// column.setLabelProvider(new RepoLabelProvider());
// }
table.viewer.setComparator(new ComparatorViewerComparator(COMPARATOR));
this.tables[r]= table;
ViewerUtils.scheduleStandardSelection(table.viewer);
}
{ final ButtonGroup<RRepo> buttons= new ButtonGroup<>(parent, this, false);
buttons.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, true, 1, 1));
buttons.addAddButton(null);
buttons.addEditButton(null);
buttons.addDeleteButton(null);
this.buttonsGroups[r]= buttons;
}
}
@Override
public RRepo edit(final int command, final RRepo item, final Object parent) {
final RRepo repo= new RRepo(((command & ButtonGroup.ADD_ANY) != 0) ? newId() : item.getId());
if (item != null) {
repo.set(item);
}
final Dialog dialog= new EditRepoDialog(getShell(), repo, item == null);
if (dialog.open() == Dialog.OK) {
if (repo.getName().isEmpty()) {
repo.setName(RRepo.hintName(repo));
}
return repo;
}
return null;
}
private String newId() {
String id;
do {
id= RRepo.CUSTOM_PREFIX + System.currentTimeMillis();
}
while (this.ids.contains(id));
return id;
}
@Override
public void updateState(final IStructuredSelection selection) {
}
@Override
protected void updateControls() {
super.updateControls();
for (int i= 0; i < R_SIZE; i++) {
this.lists[i].clear();
final List<RRepo> repos= getPreferenceValue(PREFS.get(i));
for (final RRepo repo : repos) {
this.ids.add(repo.getId());
}
this.lists[i].addAll(repos);
this.buttonsGroups[i].refresh();
}
}
@Override
protected void updatePreferences() {
for (int i= 0; i < R_SIZE; i++) {
final RRepo[] array= this.lists[i].toArray(new RRepo[this.lists[i].size()]);
Arrays.sort(array, COMPARATOR);
setPrefValue(PREFS.get(i), ImCollections.newList(array));
}
super.updatePreferences();
}
}