org.robotframework.ide.eclipse.main.plugin.preferences.InstalledRobotsPreferencesPage.java Source code

Java tutorial

Introduction

Here is the source code for org.robotframework.ide.eclipse.main.plugin.preferences.InstalledRobotsPreferencesPage.java

Source

/*
 * Copyright 2015 Nokia Solutions and Networks
 * Licensed under the Apache License, Version 2.0,
 * see license.txt file for details.
 */
package org.robotframework.ide.eclipse.main.plugin.preferences;

import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IncrementalProjectBuilder;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.QualifiedName;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.IJobChangeEvent;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.core.runtime.jobs.JobChangeAdapter;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.dialogs.ProgressMonitorDialog;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.layout.GridLayoutFactory;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.preference.PreferencePage;
import org.eclipse.jface.viewers.CheckStateChangedEvent;
import org.eclipse.jface.viewers.CheckboxTableViewer;
import org.eclipse.jface.viewers.ColumnViewerToolTipSupport;
import org.eclipse.jface.viewers.ICheckStateListener;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.ViewerColumnsFactory;
import org.eclipse.jface.viewers.ViewersConfigurator;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.DirectoryDialog;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.ProgressBar;
import org.eclipse.swt.widgets.Table;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPreferencePage;
import org.eclipse.ui.dialogs.ListSelectionDialog;
import org.rf.ide.core.executor.RobotRuntimeEnvironment;
import org.rf.ide.core.executor.RobotRuntimeEnvironment.PythonInstallationDirectory;
import org.rf.ide.core.executor.SuiteExecutor;
import org.robotframework.ide.eclipse.main.plugin.RedPlugin;
import org.robotframework.ide.eclipse.main.plugin.RedPreferences;
import org.robotframework.ide.eclipse.main.plugin.preferences.InstalledRobotsEnvironmentsLabelProvider.InstalledRobotsNamesLabelProvider;
import org.robotframework.ide.eclipse.main.plugin.preferences.InstalledRobotsEnvironmentsLabelProvider.InstalledRobotsPathsLabelProvider;
import org.robotframework.ide.eclipse.main.plugin.project.build.RobotProblem;
import org.robotframework.red.swt.SwtThread;
import org.robotframework.red.viewers.ListInputStructuredContentProvider;
import org.robotframework.red.viewers.Selections;

import com.google.common.base.Joiner;

public class InstalledRobotsPreferencesPage extends PreferencePage implements IWorkbenchPreferencePage {

    public static final String ID = "org.robotframework.ide.eclipse.main.plugin.preferences.installed";

    private List<RobotRuntimeEnvironment> installations;

    private Composite parent;
    private CheckboxTableViewer viewer;
    private ProgressBar progressBar;

    private Button addButton;
    private Button removeButton;
    private Button discoverButton;

    private boolean dirty = false;

    public InstalledRobotsPreferencesPage() {
        super("Installed Robot Frameworks");
    }

    @Override
    protected IPreferenceStore doGetPreferenceStore() {
        return RedPlugin.getDefault().getPreferenceStore();
    }

    @Override
    public void init(final IWorkbench workbench) {
        // nothing to do
    }

    @Override
    protected Control createContents(final Composite parent) {
        this.parent = parent;
        noDefaultAndApplyButton();
        GridLayoutFactory.fillDefaults().numColumns(2).applyTo(parent);

        createDescription(parent);

        createViewer(parent);

        addButton = createButton(parent, "Add...", createAddListener());
        addButton.setEnabled(false);
        addButton.setToolTipText("Choose interpreter executable directory");

        removeButton = createButton(parent, "Remove", createRemoveListener());
        removeButton.setEnabled(false);
        removeButton.setToolTipText("Remove selected environments");

        discoverButton = createButton(parent, "Discover", createDiscoverListener());
        discoverButton.setEnabled(false);
        discoverButton.setToolTipText("Search again for Python interpreters");

        createSpacer(parent);

        progressBar = createProgress(parent);

        initializeValues();
        return parent;
    }

    private SelectionListener createDiscoverListener() {
        return new SelectionAdapter() {
            @Override
            public void widgetSelected(final SelectionEvent e) {
                disableControls();
                progressBar = createProgress(parent);

                final QualifiedName key = new QualifiedName(RedPlugin.PLUGIN_ID, "result");
                final Job job = new Job("Looking for python installations") {
                    @Override
                    protected IStatus run(final IProgressMonitor monitor) {
                        addOnlyNonExisting(RobotRuntimeEnvironment.whereArePythonInterpreters());

                        setProperty(key, null);
                        return Status.OK_STATUS;
                    }
                };
                job.addJobChangeListener(new EnvironmentFoundJobListener(key));
                job.schedule();
            }

        };
    }

    private boolean addOnlyNonExisting(final Collection<PythonInstallationDirectory> locations) {
        boolean added = false;
        for (final PythonInstallationDirectory directory : locations) {
            boolean contains = false;
            for (final RobotRuntimeEnvironment environment : installations) {
                if (environment.getFile().equals(directory)) {
                    contains = true;
                    break;
                }
            }
            if (!contains) {
                added = true;
                installations.add(RobotRuntimeEnvironment.create(directory, directory.getInterpreter()));
                dirty = true;
            }
        }
        return added;
    }

    private ProgressBar createProgress(final Composite parent) {
        final ProgressBar pb = new ProgressBar(parent, SWT.SMOOTH | SWT.INDETERMINATE);
        pb.setToolTipText("Looking for Python interpreters");
        GridDataFactory.fillDefaults().span(2, 1).applyTo(pb);
        parent.layout();
        return pb;
    }

    private void destroyProgress(final ProgressBar pb) {
        final Composite parent = pb.getParent();
        pb.dispose();
        parent.layout();
    }

    private void createDescription(final Composite parent) {
        final Label lbl = new Label(parent, SWT.WRAP);
        lbl.setText(
                "Add or remove Robot frameworks environments (location of Python interpreter with Robot library "
                        + "installed, currently " + Joiner.on(", ").join(SuiteExecutor.allExecutorNames())
                        + " are supported). The selected environment will be used by project unless it is explicitly "
                        + "overridden in project configuration.");
        GridDataFactory.fillDefaults().grab(true, false).span(2, 1).hint(600, SWT.DEFAULT).applyTo(lbl);
    }

    private void createViewer(final Composite tableParent) {
        final Table table = new Table(tableParent,
                SWT.CHECK | SWT.BORDER | SWT.FULL_SELECTION | SWT.H_SCROLL | SWT.V_SCROLL);
        viewer = new CheckboxTableViewer(table);
        ViewersConfigurator.enableDeselectionPossibility(viewer);
        GridDataFactory.fillDefaults().grab(true, true).span(1, 5).applyTo(viewer.getTable());
        viewer.getTable().setLinesVisible(true);
        viewer.getTable().setHeaderVisible(true);
        final ISelectionChangedListener selectionListener = new ISelectionChangedListener() {
            @Override
            public void selectionChanged(final SelectionChangedEvent event) {
                final RobotRuntimeEnvironment selectedInstallation = getSelectedInstallation();
                removeButton.setEnabled(selectedInstallation != null);
            }
        };
        final ICheckStateListener checkListener = new ICheckStateListener() {
            @Override
            public void checkStateChanged(final CheckStateChangedEvent event) {
                if (event.getChecked()) {
                    viewer.setCheckedElements(new Object[] { event.getElement() });
                    viewer.refresh();
                }
                dirty = true;
            }
        };
        viewer.addSelectionChangedListener(selectionListener);
        viewer.addCheckStateListener(checkListener);
        viewer.getTable().addDisposeListener(new DisposeListener() {
            @Override
            public void widgetDisposed(final DisposeEvent e) {
                viewer.removeSelectionChangedListener(selectionListener);
                viewer.removeCheckStateListener(checkListener);
            }
        });
        ColumnViewerToolTipSupport.enableFor(viewer);

        viewer.setContentProvider(new ListInputStructuredContentProvider());
        ViewerColumnsFactory.newColumn("Name").withWidth(300)
                .labelsProvidedBy(new InstalledRobotsNamesLabelProvider(viewer)).createFor(viewer);
        ViewerColumnsFactory.newColumn("Path").withWidth(200)
                .labelsProvidedBy(new InstalledRobotsPathsLabelProvider(viewer)).createFor(viewer);
    }

    private Button createButton(final Composite tableParent, final String buttonLabel,
            final SelectionListener selectionListener) {
        final Button button = new Button(tableParent, SWT.PUSH);
        GridDataFactory.fillDefaults().hint(100, SWT.DEFAULT).applyTo(button);
        button.setText(buttonLabel);
        button.addSelectionListener(selectionListener);
        return button;
    }

    private void createSpacer(final Composite tableParent) {
        new Label(tableParent, SWT.NONE);
    }

    private void disableControls() {
        viewer.getTable().setEnabled(false);
        addButton.setEnabled(false);
        removeButton.setEnabled(false);
        discoverButton.setEnabled(false);
    }

    private void enableControls() {
        viewer.getTable().setEnabled(true);
        addButton.setEnabled(true);
        removeButton.setEnabled(true);
        discoverButton.setEnabled(true);
    }

    private void initializeValues() {
        final QualifiedName key = new QualifiedName(RedPlugin.PLUGIN_ID, "result");
        final Job job = new Job("Looking for python installations") {
            @Override
            protected IStatus run(final IProgressMonitor monitor) {
                final RedPreferences preferences = RedPlugin.getDefault().getPreferences();
                installations = InstalledRobotEnvironments.getAllRobotInstallation(preferences);
                final RobotRuntimeEnvironment active = InstalledRobotEnvironments
                        .getActiveRobotInstallation(preferences);
                setProperty(key, active);
                return Status.OK_STATUS;
            }
        };
        job.addJobChangeListener(new EnvironmentFoundJobListener(key));
        job.schedule();
    }

    private RobotRuntimeEnvironment getSelectedInstallation() {
        // multiselection is not possible
        final List<RobotRuntimeEnvironment> elements = Selections
                .getElements((IStructuredSelection) viewer.getSelection(), RobotRuntimeEnvironment.class);
        return elements.isEmpty() ? null : elements.get(0);
    }

    private SelectionListener createAddListener() {
        return new SelectionAdapter() {
            @Override
            public void widgetSelected(final SelectionEvent event) {
                final DirectoryDialog dirDialog = new DirectoryDialog(
                        InstalledRobotsPreferencesPage.this.getShell());
                dirDialog.setText("Browse for python installation");
                dirDialog.setMessage("Select location of python with robot framework installed");
                final String path = dirDialog.open();
                if (path != null) {
                    final List<PythonInstallationDirectory> possibleExecutors = RobotRuntimeEnvironment
                            .possibleInstallationsFor(new File(path));

                    boolean changed = false;
                    if (possibleExecutors.size() > 1) {
                        final List<PythonInstallationDirectory> toAdd = askUsersWhatShouldBeAdded(path,
                                possibleExecutors);
                        changed = addOnlyNonExisting(toAdd);
                    } else {
                        changed = true;
                        installations.add(RobotRuntimeEnvironment.create(path));
                    }
                    if (changed) {
                        dirty = true;
                        viewer.setSelection(StructuredSelection.EMPTY);
                        viewer.refresh();
                    }
                }
            }

            private List<PythonInstallationDirectory> askUsersWhatShouldBeAdded(final String path,
                    final List<PythonInstallationDirectory> possibleExecutors) {
                final List<PythonInstallationDirectory> result = new ArrayList<>();
                final ListSelectionDialog dialog = new ListSelectionDialog(getShell(), possibleExecutors,
                        new ListInputStructuredContentProvider(), new InterpretersExecutablesLabelProvider(),
                        "Select which of following interpreters detected inside '" + path + "' should be added:");
                if (dialog.open() == Window.OK) {
                    for (final Object object : dialog.getResult()) {
                        final PythonInstallationDirectory executor = (PythonInstallationDirectory) object;
                        result.add(executor);
                    }
                }
                return result;
            }
        };
    }

    private SelectionListener createRemoveListener() {
        return new SelectionAdapter() {
            @Override
            public void widgetSelected(final SelectionEvent event) {
                final RobotRuntimeEnvironment env = getSelectedInstallation();
                installations.remove(env);

                dirty = true;
                viewer.setSelection(StructuredSelection.EMPTY);
                viewer.refresh();
            }
        };
    }

    @Override
    public boolean performOk() {
        if (dirty) {
            final Object[] checkedElement = viewer.getCheckedElements();
            final RobotRuntimeEnvironment checkedEnv = checkedElement.length == 0 ? null
                    : (RobotRuntimeEnvironment) checkedElement[0];

            final List<String> allPathsList = new ArrayList<>();
            final List<String> allExecsList = new ArrayList<>();

            for (final RobotRuntimeEnvironment installation : installations) {
                allPathsList.add(installation.getFile().getAbsolutePath());
                allExecsList.add(getExecOf(installation));
            }

            final String activePath = checkedEnv == null ? "" : checkedEnv.getFile().getAbsolutePath();
            final String activeExec = checkedEnv == null ? "" : getExecOf(checkedEnv);
            final String allPaths = Joiner.on(';').join(allPathsList);
            final String allExecs = Joiner.on(";").join(allExecsList);

            // The execs has to be stored first, because we're listening on ACTIVE_RUNTIMES
            // and OTHER_RUNTIMES changes and inside we need actaul value of corresponding
            // execs preference. This may seem a bit weird to have separated ACTIVE_RUNTIME and
            // ACTIVE_RUNTIME_EXEC pair, but implementing it this way gives us both directions
            // versions compatibility.
            getPreferenceStore().putValue(RedPreferences.ACTIVE_RUNTIME_EXEC, activeExec);
            getPreferenceStore().putValue(RedPreferences.OTHER_RUNTIMES_EXECS, allExecs);

            getPreferenceStore().putValue(RedPreferences.ACTIVE_RUNTIME, activePath);
            getPreferenceStore().putValue(RedPreferences.OTHER_RUNTIMES, allPaths);

            MessageDialog.openInformation(getShell(), "Rebuild required",
                    "The changes you've made requires full workspace rebuild.");

            rebuildWorkspace();
            return true;
        } else {
            return true;
        }
    }

    private String getExecOf(final RobotRuntimeEnvironment installation) {
        return installation.getFile() instanceof PythonInstallationDirectory ? installation.getInterpreter().name()
                : "";
    }

    private void rebuildWorkspace() {
        try {
            new ProgressMonitorDialog(getShell()).run(true, true, monitor -> {
                for (final IProject project : ResourcesPlugin.getWorkspace().getRoot().getProjects(0)) {
                    try {
                        if (project.exists() && project.isOpen()) {
                            project.deleteMarkers(RobotProblem.TYPE_ID, true, IResource.DEPTH_INFINITE);
                            project.build(IncrementalProjectBuilder.FULL_BUILD, null);
                        }
                    } catch (final CoreException e) {
                        MessageDialog.openError(getShell(), "Workspace rebuild",
                                "Problems occurred during workspace build " + e.getMessage());
                    }
                }
            });
        } catch (InvocationTargetException | InterruptedException e) {
            MessageDialog.openError(getShell(), "Workspace rebuild",
                    "Problems occurred during workspace build " + e.getMessage());
        }
    }

    private final class EnvironmentFoundJobListener extends JobChangeAdapter {

        private final QualifiedName key;

        private EnvironmentFoundJobListener(final QualifiedName key) {
            this.key = key;
        }

        @Override
        public void done(final IJobChangeEvent event) {
            SwtThread.asyncExec(new Runnable() {
                @Override
                public void run() {
                    final RobotRuntimeEnvironment active = (RobotRuntimeEnvironment) event.getJob()
                            .getProperty(key);
                    final Control control = InstalledRobotsPreferencesPage.this.getControl();
                    if (control == null || control.isDisposed()) {
                        return;
                    }
                    enableControls();
                    viewer.setInput(installations);
                    if (active != null) {
                        viewer.setChecked(active, true);
                        viewer.refresh();
                    }
                    viewer.setSelection(StructuredSelection.EMPTY);

                    destroyProgress(progressBar);
                    progressBar = null;
                }
            });
        }
    }

    private static class InterpretersExecutablesLabelProvider extends LabelProvider {

        @Override
        public String getText(final Object element) {
            return ((PythonInstallationDirectory) element).getInterpreter().executableName();
        }
    }
}