com.google.eclipse.mechanic.plugin.ui.MechanicStatusControlContribution.java Source code

Java tutorial

Introduction

Here is the source code for com.google.eclipse.mechanic.plugin.ui.MechanicStatusControlContribution.java

Source

/*******************************************************************************
 * Copyright (C) 2011, Google 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
 *******************************************************************************/
package com.google.eclipse.mechanic.plugin.ui;

import com.google.common.collect.Maps;
import com.google.eclipse.mechanic.IMechanicService;
import com.google.eclipse.mechanic.IStatusChangeListener;
import com.google.eclipse.mechanic.MechanicService;
import com.google.eclipse.mechanic.MechanicStatus;
import com.google.eclipse.mechanic.RepairDecisionProvider;
import com.google.eclipse.mechanic.StatusChangedEvent;
import com.google.eclipse.mechanic.core.recorder.ChangeCollector;
import com.google.eclipse.mechanic.core.recorder.IPreferenceRecordingService;
import com.google.eclipse.mechanic.plugin.core.MechanicLog;
import com.google.eclipse.mechanic.plugin.core.MechanicPlugin;
import com.google.eclipse.mechanic.plugin.core.OldMechanicPreferences;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.RowLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IWorkbenchActionConstants;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.menus.WorkbenchWindowControlContribution;

import java.net.MalformedURLException;
import java.util.Map;

/**
 * Widget that appears in the status bar.
 */
public class MechanicStatusControlContribution extends WorkbenchWindowControlContribution {

    private static final MechanicLog log = MechanicLog.getDefault();

    // various actions used in our context menu...
    private static final Action prefsAction = new OpenPreferencesAction(
            "com.google.eclipse.mechanic.plugin.ui.MechanicPreferencePage");

    private static final Action helpAction = createHelpAction();

    private final Action invokeScannerAction = new InvokeScannerAction();

    private final Action repairAction = new InvokeRepairManagerAction();

    private final StopAction stopAction = new StopAction();

    private final StartAction startAction = new StartAction();

    private IAction startRecordingAction = new StartRecordingAction();

    private IAction stopRecordingAction = new StopRecordingAction();

    private IAction showAllTasksAction = new ShowAllTasksAction();

    private IAction dumpKeybindingsAction = new DumpKeyBindingsAction();

    private final IMechanicService service = MechanicService.getInstance();

    private final IStatusChangeListener statusListener;

    private final IPreferenceRecordingService preferenceRecordingService;

    private MechanicStatus status;
    private Label label;
    private Map<DisplayStatus, Image> images;

    public MechanicStatusControlContribution() {
        this.preferenceRecordingService = MechanicPlugin.getDefault().getPreferenceRecordingService();
        // to be registered in our initialize method, and disposed of with us
        this.statusListener = new IStatusChangeListener() {
            public void statusChanged(StatusChangedEvent e) {
                setMechanicStatus(e.getStatus());
                updateDisplay();
            }
        };
        updateRecordingStatusMenuItems(preferenceRecordingService.isRecording());
    }

    private static Action createHelpAction() {
        String helpUrl = OldMechanicPreferences.getHelpUrl();
        try {
            return new OpenUrlAction(helpUrl, "Help...");
        } catch (MalformedURLException e) {
            log.logError(e, "Could not initialize help action for URL %s: %s", helpUrl, e.getMessage());
            return null;
        }
    }

    @Override
    protected Control createControl(Composite parent) {
        initImageCache();

        label = new Label(parent, SWT.CENTER);
        label.setSize(22, 22);
        label.setEnabled(false);

        RowLayout layout = new RowLayout(SWT.HORIZONTAL | SWT.VERTICAL | SWT.BORDER);
        layout.wrap = false;
        parent.setLayout(layout);

        setMechanicStatus(MechanicStatus.STOPPED);

        // observe mouse events
        registerClickHandlers();

        createContextMenu(label);

        // observe TaskService status changes
        service.addTaskStatusChangeListener(statusListener);

        // TODO(smckay): figure out why updateDisplay must be called here and now
        // or else, the widget contents (label) will *never* get displayed.
        updateDisplay();
        return label;
    }

    /**
     * Initializes the image cache.
     */
    private void initImageCache() {
        Map<DisplayStatus, Image> map = Maps.newHashMap();

        for (DisplayStatus ds : DisplayStatus.values()) {
            String path = String.format("icons/%s.png", ds.name().toLowerCase());
            ImageDescriptor desc = MechanicPlugin.getImageDescriptor(path);
            if (desc == null) {
                MechanicLog.getDefault().log(new Status(IStatus.ERROR, MechanicPlugin.PLUGIN_ID,
                        "Can't find image descriptor for resource " + path));
            } else {
                Image image = desc.createImage();
                map.put(ds, image);
            }
        }
        images = map;
    }

    private void updateDisplay() {
        /*
         * Don't do anything if the workbench is closed. Without this check we were
         * causing stack traces when workbench is closing.
         */
        if (this.getWorkbenchWindow().getWorkbench().isClosing()) {
            return;
        }

        if (!label.isDisposed()) {
            DisplayStatus ds = getDisplayStatus();

            // as this may be the first update we've received, we enable our labels
            label.setEnabled(true);
            label.setImage(images.get(ds));
            label.setToolTipText(ds.toString());
        }
    }

    private void setMechanicStatus(MechanicStatus status) {
        this.status = status;
    }

    private void registerClickHandlers() {

        MouseListener listener = new MouseListener() {

            public void mouseDoubleClick(MouseEvent e) {

                // if the conditions are right, rerun the service
                if (!service.isStopped() && status != MechanicStatus.FAILED && status != MechanicStatus.UPDATING) {
                    invokeScannerAction.run();
                }
            }

            public void mouseUp(MouseEvent e) {

                // if the status is failed, we work with the user to repair failing
                // actions.
                if (!service.isStopped() && status == MechanicStatus.FAILED) {
                    repairAction.run();
                }
            }

            // ignored
            public void mouseDown(MouseEvent e) {
            }

        };

        label.addMouseListener(listener);
    }

    /**
     * Re-maps the current TaskStatus to a display status. DisplayStatus provides
     * UI friendly serialization of the state.
     */
    private DisplayStatus getDisplayStatus() {
        switch (status) {
        case PASSED:
            return DisplayStatus.PASSED;
        case FAILED:
            return DisplayStatus.FAILED;
        case UPDATING:
            return DisplayStatus.UPDATING;
        case STOPPED:
            return DisplayStatus.STOPPED;
        }
        throw new RuntimeException("Unhandled status value: " + status.name());
    }

    @Override
    public void dispose() {

        label.dispose();

        for (Image image : images.values()) {
            image.dispose();
        }

        // un-register any Task Service listeners
        service.removeTaskStatusChangeListener(statusListener);
    }

    /**
     * Creates and registers a new popup menu on the supplied control.
     * 
     * <p>
     * When the menu is about to be shown the
     * {@link #fillContextMenu(IMenuManager)} method will be called.
     */
    private void createContextMenu(Control control) {
        MenuManager mgr = new MenuManager("#PopupMenu");
        mgr.setRemoveAllWhenShown(true);
        mgr.addMenuListener(new IMenuListener() {
            public void menuAboutToShow(IMenuManager manager) {
                fillContextMenu(manager);
            }
        });

        Menu menu = mgr.createContextMenu(control);
        control.setMenu(menu);
    }

    /**
     * Fills the context menu with menu items.
     */
    private void fillContextMenu(IMenuManager menu) {

        // add the individual menu items
        if (service.isStopped()) {
            menu.add(startAction);
        } else {
            menu.add(invokeScannerAction);
            if (status == MechanicStatus.FAILED) {
                menu.add(repairAction);
            }
            menu.add(stopAction);
        }

        menu.add(new Separator());

        IMenuManager recordingMenu = new MenuManager("Preference Recorder");
        recordingMenu.add(startRecordingAction);
        recordingMenu.add(stopRecordingAction);

        menu.add(recordingMenu);

        menu.add(showAllTasksAction);

        menu.add(dumpKeybindingsAction);

        menu.add(new Separator());
        menu.add(prefsAction);
        if (helpAction != null) {
            menu.add(helpAction);
        }

        // an obligatory menu item that allows other code to add items to
        // out context menu.
        menu.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS));
    }

    /**
     * Representation of the current state of the mechanic.
     */
    public enum DisplayStatus {

        PASSED("All tasks passed. Double click to re-check."), FAILED(new Object() {
            @Override
            public String toString() {
                return String.format("%d failing tasks. Click to fix.",
                        MechanicService.getInstance().getFailingItemCount());
            }
        }), UPDATING("Updating..."), STOPPED("Service is stopped.");

        private final Object describer;

        private DisplayStatus(Object describer) {
            this.describer = describer;
        }

        @Override
        public String toString() {
            return describer.toString();
        }
    }

    /**
     * Action that causes the mechanic to recheck all tasks.
     */
    private final class InvokeScannerAction extends Action {

        public InvokeScannerAction() {
            setText("Check All");
        }

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

        /**
         * Runs the mechanic immediately.
         */
        private void invokeMechanic() {
            service.start();
        }
    }

    /**
     * Action that causes the mechanic to re-check all tasks.
     */
    private final class InvokeRepairManagerAction extends Action {

        public InvokeRepairManagerAction() {
            setText("Apply Fixes...");
        }

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

        /**
         * Prompts the user for which failing actions they should repair, then
         * repairs them.
         */
        private void invokeRepairManager() {
            RepairDecisionProvider cpro = new UserChoiceDecisionProvider(
                    PlatformUI.getWorkbench().getActiveWorkbenchWindow());
            // TODO(konigsberg): Replace with calls to runRepairManager?
            service.getRepairManager(cpro).run();
        }
    }

    /**
     * Action that starts the mechanic service.
     */
    private final class StartAction extends Action {

        public StartAction() {
            setText("Start Service");
        }

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

        /**
         * Stops the service.
         */
        private void start() {
            service.start();
        }
    }

    /**
     * Action that stops the mechanic service.
     */
    private final class StopAction extends Action {

        public StopAction() {
            setText("Stop Service");
        }

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

        /**
         * Stops the service.
         */
        private void stop() {
            service.stop();
        }
    }

    private void updateRecordingStatusMenuItems(boolean recording) {
        startRecordingAction.setEnabled(!recording);
        stopRecordingAction.setEnabled(recording);
    }

    /**
     * Action to start recording preferences.
     */
    public class StartRecordingAction extends Action {

        public StartRecordingAction() {
            super("Start Recording");
        }

        @Override
        public void run() {
            try {
                updateRecordingStatusMenuItems(true);

                preferenceRecordingService.startRecording();
            } catch (CoreException e) {
                MechanicPlugin.getDefault().getLog().log(e.getStatus());
            }
        }
    }

    /**
     * Action to stop recording preferences and as the user how to re
     */
    public class StopRecordingAction extends Action {
        public StopRecordingAction() {
            super("Stop Recording...");
        }

        @Override
        public void run() {
            try {
                // Stop recording, and collect results
                ChangeCollector collector = new ChangeCollector();
                preferenceRecordingService.endRecording(collector);

                updateRecordingStatusMenuItems(false);

                // Display dialog to get obtain properties of the saved task file
                Shell parentShell = Display.getCurrent().getActiveShell();
                Map<String, String> preferences = collector.getPreferences();
                if (preferences.isEmpty()) {
                    MessageDialog.openWarning(parentShell, "No preferences",
                            "No changes to preferences were made.");
                } else {
                    EpfOutputDialog dialog = new EpfOutputDialog(parentShell, preferences);
                    dialog.open();
                }
            } catch (CoreException e) {
                MechanicLog.getDefault().logError(e);
            }
        }
    }

    /**
     * Action that displays all existing tasks.
     */
    private static final class ShowAllTasksAction extends Action {

        public ShowAllTasksAction() {
            setText("Show all tasks");
        }

        @Override
        public void run() {
            // Display dialog to get obtain properties of the saved task file
            Shell parentShell = Display.getCurrent().getActiveShell();
            TaskSelectionDialog dialog = new TaskSelectionDialog(parentShell, "All known tasks",
                    MechanicService.getInstance().getAllKnownTasks());
            dialog.setAddCancelButton(false);
            dialog.open();
        }
    }

    /**
     * Action that dumps the existing keybindings to an export file.
     */
    private static final class DumpKeyBindingsAction extends Action {

        public DumpKeyBindingsAction() {
            super("Dump keybindings...");
        }

        @Override
        public void run() {
            // Display dialog to get obtain properties of the saved task file
            Shell parentShell = Display.getCurrent().getActiveShell();
            new KeybindingsOutputDialog(parentShell).open();
        }
    }
}