org.eclipse.thym.ui.internal.engine.AvailableCordovaEnginesSection.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.thym.ui.internal.engine.AvailableCordovaEnginesSection.java

Source

/*******************************************************************************
 * Copyright (c) 2013, 2016 Red Hat, Inc.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 *    Contributors:
 *        Red Hat Inc. - initial API and implementation and/or initial documentation
 *******************************************************************************/
package org.eclipse.thym.ui.internal.engine;

import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.ListenerList;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.dialogs.ErrorDialog;
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.operation.IRunnableWithProgress;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.resource.FontDescriptor;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.viewers.CheckStateChangedEvent;
import org.eclipse.jface.viewers.CheckboxTreeViewer;
import org.eclipse.jface.viewers.ICheckStateListener;
import org.eclipse.jface.viewers.IFontProvider;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITableLabelProvider;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerComparator;
import org.eclipse.jface.window.Window;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.DirectoryDialog;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeColumn;
import org.eclipse.thym.core.HybridCore;
import org.eclipse.thym.core.engine.HybridMobileEngine;
import org.eclipse.thym.core.engine.HybridMobileEngineLocator;
import org.eclipse.thym.core.engine.HybridMobileEngineLocator.EngineSearchListener;
import org.eclipse.thym.core.engine.internal.cordova.CordovaEngineProvider;
import org.eclipse.thym.core.extensions.PlatformSupport;
import org.eclipse.thym.core.platform.PlatformConstants;
import org.eclipse.thym.ui.HybridUI;
import org.eclipse.thym.ui.PlatformImage;
import org.eclipse.thym.ui.internal.status.StatusManager;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.forms.widgets.FormToolkit;
import org.eclipse.ui.services.IEvaluationService;

import com.github.zafarkhaja.semver.ParseException;
import com.github.zafarkhaja.semver.Version;

public class AvailableCordovaEnginesSection implements ISelectionProvider {

    private static final int TREE_HEIGHT = 250;
    private static final int TREE_WIDTH = 500;

    private ListenerList selectionListeners;
    private ListenerList engineChangeListeners;
    private CheckboxTreeViewer engineList;
    private ISelection prevSelection = new StructuredSelection();
    private CordovaEngineProvider provider;
    private Button removeBtn;
    private FormToolkit formToolkit;

    public static interface EngineListChangeListener {
        public void listChanged();
    }

    private static class CordovaEnginesContentProvider implements ITreeContentProvider {
        private List<HybridMobileEngine> engines;
        private PlatformSupport[] platforms;

        @Override
        public void dispose() {
        }

        @Override
        public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
            this.engines = (List<HybridMobileEngine>) newInput;
        }

        @Override
        public Object[] getElements(Object inputElement) {
            if (platforms == null) {
                List<PlatformSupport> allPlatforms = HybridCore.getPlatformSupports();
                ArrayList<PlatformSupport> elements = new ArrayList<PlatformSupport>();
                IEvaluationService service = (IEvaluationService) PlatformUI.getWorkbench()
                        .getService(IEvaluationService.class);
                for (PlatformSupport generator : allPlatforms) {
                    try {
                        if (generator.isEnabled(service.getCurrentState())) {
                            elements.add(generator);
                        }

                    } catch (CoreException e) {
                        HybridUI.log(IStatus.ERROR, "Error filtering objects", e);
                    }
                }
                platforms = elements.toArray(new PlatformSupport[elements.size()]);
            }
            return platforms;
        }

        @Override
        public Object[] getChildren(Object parentElement) {
            if (engines == null || !(parentElement instanceof PlatformSupport)) {
                return null;
            }
            PlatformSupport platform = (PlatformSupport) parentElement;
            ArrayList<HybridMobileEngine> platformEngines = new ArrayList<HybridMobileEngine>();
            for (HybridMobileEngine hybridMobileEngine : engines) {
                if (hybridMobileEngine.getId().equals(platform.getPlatformId())) {
                    platformEngines.add(hybridMobileEngine);
                }
            }
            return platformEngines.toArray(new HybridMobileEngine[platformEngines.size()]);
        }

        @Override
        public Object getParent(Object element) {
            if (platforms != null && element instanceof HybridMobileEngine) {
                HybridMobileEngine engine = (HybridMobileEngine) element;
                for (PlatformSupport platformSupport : platforms) {
                    if (engine.getId().equals(platformSupport.getPlatformId())) {
                        return platformSupport;
                    }
                }
            }
            return null;
        }

        @Override
        public boolean hasChildren(Object element) {
            return element instanceof PlatformSupport;
        }

    }

    private class CordovaEngineLabelProvider extends LabelProvider implements ITableLabelProvider, IFontProvider {
        private Font boldFont;

        @Override
        public Image getImage(Object element) {
            if (element instanceof PlatformSupport) {
                PlatformSupport platform = (PlatformSupport) element;
                return PlatformImage.getImageFor(PlatformImage.ATTR_PLATFORM_SUPPORT, platform.getID());
            }
            return null;
        }

        @Override
        public String getText(Object element) {
            if (element instanceof PlatformSupport) {
                PlatformSupport platform = (PlatformSupport) element;
                return platform.getPlatform();
            }
            if (element instanceof HybridMobileEngine) {
                HybridMobileEngine engine = (HybridMobileEngine) element;
                String identifier = engine.isManaged() ? engine.getVersion() : engine.getLocation().toString();
                return NLS.bind("{0}@{1}", new String[] { engine.getName(), identifier });
            }
            return null;
        }

        private String getLocationText(Object element) {
            if (element instanceof HybridMobileEngine) {
                HybridMobileEngine engine = (HybridMobileEngine) element;
                return engine.getLocation().toOSString();
            }
            return null;
        }

        @Override
        public Font getFont(Object element) {
            if (!engineList.getChecked(element))
                return null;
            if (boldFont == null) {
                FontDescriptor fontDescriptor = JFaceResources.getDialogFontDescriptor();
                fontDescriptor = fontDescriptor.setStyle(SWT.BOLD);
                boldFont = fontDescriptor.createFont(Display.getCurrent());
            }
            return boldFont;
        }

        @Override
        public String getColumnText(Object element, int columnIndex) {
            switch (columnIndex) {
            case 0:
                return getText(element);
            case 1:
                return getLocationText(element);
            default:
                return "invalid";
            }
        }

        @Override
        public Image getColumnImage(Object element, int columnIndex) {
            if (columnIndex == 0) {
                return getImage(element);
            }
            return null;
        }

    }

    private static class EngineVersionComparator extends ViewerComparator
            implements Comparator<HybridMobileEngine> {
        private boolean descending = true;

        public EngineVersionComparator(boolean isDescending) {
            descending = isDescending;
        }

        @Override
        public int compare(Viewer viewer, Object o1, Object o2) {
            if (!(o1 instanceof HybridMobileEngine)) {
                return 1;
            }
            HybridMobileEngine e1 = (HybridMobileEngine) o1;
            HybridMobileEngine e2 = (HybridMobileEngine) o2;
            return compare(e1, e2);
        }

        @Override
        public int compare(HybridMobileEngine o1, HybridMobileEngine o2) {
            try {
                Version version1 = Version.valueOf(o1.getVersion());
                Version version2 = Version.valueOf(o2.getVersion());
                if (descending) {
                    return version2.compareTo(version1);
                }
                return version1.compareTo(version2);
            } catch (ParseException e) {
                return 1;
            }
        }
    }

    public AvailableCordovaEnginesSection() {
        this.selectionListeners = new ListenerList();
        this.engineChangeListeners = new ListenerList();
        this.formToolkit = null;
    }

    public AvailableCordovaEnginesSection(FormToolkit formToolkit) {
        this();
        this.formToolkit = formToolkit;
    }

    public void createControl(final Composite parent) {
        Composite composite = new Composite(parent, SWT.NONE);
        GridLayoutFactory.fillDefaults().numColumns(2).applyTo(composite);
        GridDataFactory.fillDefaults().align(SWT.FILL, SWT.FILL).grab(true, true).applyTo(composite);

        Label tableLbl = new Label(composite, SWT.NULL);
        tableLbl.setText("Available Engines: ");
        GridDataFactory.generate(tableLbl, 2, 1);

        Tree tree = new Tree(composite, SWT.CHECK | SWT.FULL_SELECTION);
        GridDataFactory.fillDefaults().align(SWT.FILL, SWT.FILL).grab(true, true)
                .minSize(new Point(TREE_WIDTH, TREE_HEIGHT)).applyTo(tree);

        tree.setHeaderVisible(true);
        tree.setLinesVisible(true);
        TreeColumn col_0 = new TreeColumn(tree, SWT.LEFT);
        col_0.setText("Engine");
        col_0.setWidth(TREE_WIDTH / 2);
        col_0.setMoveable(false);
        TreeColumn col_1 = new TreeColumn(tree, SWT.LEFT);
        col_1.setText("Location");
        col_1.setWidth(TREE_WIDTH / 2);
        col_1.setMoveable(false);

        engineList = new CheckboxTreeViewer(tree);
        engineList.setContentProvider(new CordovaEnginesContentProvider());
        engineList.setLabelProvider(new CordovaEngineLabelProvider());
        engineList.setComparator(new EngineVersionComparator(true));
        engineList.setUseHashlookup(true);

        engineList.addCheckStateListener(new ICheckStateListener() {
            public void checkStateChanged(CheckStateChangedEvent event) {
                if (event.getChecked()) {
                    ITreeContentProvider cp = (ITreeContentProvider) engineList.getContentProvider();
                    if (event.getElement() instanceof PlatformSupport) {
                        engineList.setChecked(event.getElement(), false);
                        HybridMobileEngine[] children = (HybridMobileEngine[]) cp.getChildren(event.getElement());
                        if (children != null && children.length > 0) {
                            //Sort so that we can select the highest version number.
                            Arrays.sort(children, new EngineVersionComparator(true));
                            engineList.setChecked(children[0], true);
                            for (int i = 1; i < children.length; i++) {//start with index 1
                                engineList.setChecked(children[i], false);
                            }
                        }
                    } else {
                        Object[] siblings = cp.getChildren(cp.getParent(event.getElement()));
                        for (int i = 0; i < siblings.length; i++) {
                            if (siblings[i] != event.getElement()) {
                                engineList.setChecked(siblings[i], false);
                            }
                        }
                    }

                }
                Object[] checked = engineList.getCheckedElements();
                if (checked != null && checked.length > 0) {
                    setSelection(new StructuredSelection(checked));
                } else {
                    setSelection(new StructuredSelection());
                }
            }
        });

        engineList.addSelectionChangedListener(new ISelectionChangedListener() {

            @Override
            public void selectionChanged(SelectionChangedEvent event) {
                updateButtons();

            }
        });

        Composite buttonsContainer = new Composite(composite, SWT.NONE);
        GridDataFactory.fillDefaults().align(SWT.BEGINNING, SWT.FILL).applyTo(buttonsContainer);
        GridLayoutFactory.fillDefaults().spacing(0, 3).numColumns(1).applyTo(buttonsContainer);

        Button downloadBtn = new Button(buttonsContainer, SWT.PUSH);
        downloadBtn.setText("Download...");
        GridDataFactory.fillDefaults().align(SWT.FILL, SWT.BEGINNING).applyTo(downloadBtn);
        ;
        downloadBtn.addListener(SWT.Selection, new Listener() {

            @Override
            public void handleEvent(Event event) {
                EngineDownloadDialog downloadDialog = new EngineDownloadDialog(engineList.getControl().getShell());
                int status = downloadDialog.open();
                if (status == Window.OK) {
                    updateAvailableEngines();
                }

            }
        });

        Button searchBtn = new Button(buttonsContainer, SWT.PUSH);
        searchBtn.setText("Search...");
        GridDataFactory.fillDefaults().align(SWT.FILL, SWT.BEGINNING).applyTo(searchBtn);
        searchBtn.addListener(SWT.Selection, new Listener() {

            @Override
            public void handleEvent(Event event) {
                handleSearch(parent);
            }
        });

        removeBtn = new Button(buttonsContainer, SWT.PUSH);
        removeBtn.setText("Remove");
        GridDataFactory.fillDefaults().align(SWT.FILL, SWT.BEGINNING).applyTo(removeBtn);
        removeBtn.addListener(SWT.Selection, new Listener() {

            @Override
            public void handleEvent(Event event) {
                handleRemoveEngine();
            }
        });

        updateAvailableEngines();
        updateButtons();

        if (formToolkit != null) {
            formToolkit.adapt(composite);
            formToolkit.paintBordersFor(composite);
            formToolkit.adapt(tree, true, false);
            formToolkit.adapt(buttonsContainer);
            formToolkit.adapt(downloadBtn, true, false);
            formToolkit.adapt(searchBtn, true, false);
            formToolkit.adapt(removeBtn, true, false);
        }
    }

    private void updateButtons() {
        IStructuredSelection selection = (IStructuredSelection) engineList.getSelection();
        removeBtn.setEnabled(!selection.isEmpty() && selection.getFirstElement() instanceof HybridMobileEngine);
    }

    private void updateAvailableEngines() {
        CordovaEngineProvider provider = getEngineProvider();
        final List<HybridMobileEngine> engines = provider.getAvailableEngines();
        Job preCompileJob = new Job("Hybrid Mobile Engine Library pre-compilation") {

            @Override
            protected IStatus run(IProgressMonitor monitor) {
                for (HybridMobileEngine hybridMobileEngine : engines) {
                    try {
                        hybridMobileEngine.preCompile(monitor);
                    } catch (CoreException e) {
                        // Because at this point whether the engine is going to 
                        // be used or not is not determined yet, we can ignore 
                        // the pre-compilation problems
                    }
                }
                return Status.OK_STATUS;
            }
        };
        preCompileJob.schedule();
        engineList.setInput(engines);
        fireEngineListChanged();

    }

    private CordovaEngineProvider getEngineProvider() {
        if (provider == null) {
            provider = new CordovaEngineProvider();
        }
        return provider;
    }

    @Override
    public void addSelectionChangedListener(ISelectionChangedListener listener) {
        selectionListeners.add(listener);
    }

    public void addEngineListChangeListener(EngineListChangeListener listener) {
        engineChangeListeners.add(listener);
    }

    public void removeEngineListChangeListener(EngineListChangeListener listener) {
        engineChangeListeners.remove(listener);
    }

    @Override
    public ISelection getSelection() {
        return new StructuredSelection(engineList.getCheckedElements());
    }

    /**
     * Returns the list of {@link HybridMobileEngine}s that 
     * are listed null if the list is empty.
     * @return
     */
    public List<HybridMobileEngine> getListedEngines() {
        Object o = engineList.getInput();
        if (o == null)
            return null;
        return (List<HybridMobileEngine>) o;

    }

    @Override
    public void removeSelectionChangedListener(ISelectionChangedListener listener) {
        selectionListeners.remove(selectionListeners);
    }

    @Override
    public void setSelection(ISelection selection) {
        if (selection instanceof IStructuredSelection) {
            if (!selection.equals(prevSelection)) {
                prevSelection = selection;
                engineList.setCheckedElements(new Object[0]);
                if (selection.isEmpty()) {
                    engineList.setCheckedElements(new Object[0]);
                } else {
                    IStructuredSelection structuredSelection = (IStructuredSelection) selection;
                    for (Iterator<?> iterator = structuredSelection.iterator(); iterator.hasNext();) {
                        HybridMobileEngine engine = (HybridMobileEngine) iterator.next();
                        engineList.setChecked(engine, true);
                        engineList.reveal(engine);
                    }
                }
                engineList.refresh(true);
                fireSelectionChanged();
            }
        }
    }

    private void fireSelectionChanged() {
        SelectionChangedEvent event = new SelectionChangedEvent(this, getSelection());
        Object[] listeners = selectionListeners.getListeners();
        for (int i = 0; i < listeners.length; i++) {
            ISelectionChangedListener listener = (ISelectionChangedListener) listeners[i];
            listener.selectionChanged(event);
        }
    }

    private void fireEngineListChanged() {
        Object[] listeners = engineChangeListeners.getListeners();
        for (int i = 0; i < listeners.length; i++) {
            EngineListChangeListener l = (EngineListChangeListener) listeners[i];
            l.listChanged();
        }
    }

    private void handleSearch(final Composite parent) {
        DirectoryDialog directoryDialog = new DirectoryDialog(parent.getShell());
        directoryDialog.setMessage("Select the directory in which to search for hybrid mobile engines");
        directoryDialog.setText("Search for Hybrid Mobile Engines");

        String pathStr = directoryDialog.open();
        if (pathStr == null)
            return;

        final IPath path = new Path(pathStr);
        final ProgressMonitorDialog dialog = new ProgressMonitorDialog(parent.getShell());
        dialog.setBlockOnOpen(false);
        dialog.setCancelable(true);
        dialog.open();
        final EngineSearchListener listener = new EngineSearchListener() {

            @Override
            public void engineFound(HybridMobileEngine engine) {
                addPathToPreference(engine.getLocation());
                getEngineProvider().engineFound(engine);
            }
        };

        IRunnableWithProgress runnable = new IRunnableWithProgress() {

            @Override
            public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
                List<HybridMobileEngineLocator> locators = HybridCore.getEngineLocators();
                for (HybridMobileEngineLocator locator : locators) {
                    locator.searchForRuntimes(path, listener, monitor);
                }
                parent.getDisplay().asyncExec(new Runnable() {

                    @Override
                    public void run() {
                        updateAvailableEngines();
                    }
                });
            }
        };

        try {
            dialog.run(true, true, runnable);
        } catch (InvocationTargetException e) {
            if (e.getTargetException() != null) {
                if (e.getTargetException() instanceof CoreException) {
                    StatusManager.handle((CoreException) e.getTargetException());
                } else {
                    ErrorDialog.openError(parent.getShell(), "Local Engine Search Error", null,
                            new Status(IStatus.ERROR, HybridUI.PLUGIN_ID,
                                    "Error when searching for local hybrid mobile engines",
                                    e.getTargetException()));
                }
            }
        } catch (InterruptedException e) {
            HybridUI.log(IStatus.ERROR, "Search for Cordova Engines error", e);
        }
    }

    private void addPathToPreference(IPath path) {
        IPreferenceStore store = HybridUI.getDefault().getPreferenceStore();
        String locs = store.getString(PlatformConstants.PREF_CUSTOM_LIB_LOCS);
        String pathString = path.toString();
        if (locs == null || locs.isEmpty()) {
            locs = pathString;
        } else {
            String[] locArray = locs.split(",");
            for (int i = 0; i < locArray.length; i++) {//Check for duplicates
                if (locArray[i].equals(pathString)) {
                    return;
                }
            }
            locs += "," + pathString;
        }
        store.setValue(PlatformConstants.PREF_CUSTOM_LIB_LOCS, locs);
    }

    private void removePathFromPreference(IPath path) {
        IPreferenceStore store = HybridUI.getDefault().getPreferenceStore();
        String locs = store.getString(PlatformConstants.PREF_CUSTOM_LIB_LOCS);
        if (locs == null || locs.isEmpty())
            return;
        String pathString = path.toString();
        String[] locArray = locs.split(",");
        StringBuilder newLocs = new StringBuilder();
        for (int i = 0; i < locArray.length; i++) {
            if (locArray[i].equals(pathString)) {
                continue;
            }
            if (i > 0)
                newLocs.append(",");
            newLocs.append(locArray[i]);
        }
        store.setValue(PlatformConstants.PREF_CUSTOM_LIB_LOCS, newLocs.toString());
    }

    private void handleRemoveEngine() {
        IStructuredSelection selection = (IStructuredSelection) engineList.getSelection();
        if (selection.isEmpty())
            return;
        Object selectedObject = selection.getFirstElement();
        if (!(selectedObject instanceof HybridMobileEngine)) {
            return;
        }
        HybridMobileEngine selectedEngine = (HybridMobileEngine) selectedObject;
        boolean deleteConfirm = MessageDialog.openConfirm(this.engineList.getTree().getShell(), "Confirm Delete",
                NLS.bind("This will remove {0} {1} from your computer. Do you want to continue?",
                        new String[] { selectedEngine.getName(), selectedEngine.getVersion() }));
        if (deleteConfirm) {
            getEngineProvider().deleteEngineLibraries(selectedEngine);
            if (selectedEngine.getId().equals(CordovaEngineProvider.CUSTOM_CORDOVA_ENGINE_ID)) {//Update the prefs for custom engines.
                removePathFromPreference(selectedEngine.getLocation());
            }
            updateAvailableEngines();
            ISelection cSelection = getSelection();
            HybridMobileEngine checkedEngine = null;
            if (cSelection != null && !cSelection.isEmpty()) {
                IStructuredSelection css = (IStructuredSelection) cSelection;
                checkedEngine = (HybridMobileEngine) css.getFirstElement();
            }
            if (checkedEngine != null && checkedEngine == selectedEngine) {
                setSelection(new StructuredSelection());
            }
        }
    }

}