/* * Copyright 2013 Amazon Technologies, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://aws.amazon.com/apache2.0 * * This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES * OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and * limitations under the License. */ package com.amazonaws.eclipse.dynamodb.testtool; import java.util.Collection; import java.util.LinkedList; import java.util.List; import org.eclipse.core.databinding.observable.value.IObservableValue; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.jface.action.Action; import org.eclipse.jface.action.ActionContributionItem; 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.action.ToolBarManager; import org.eclipse.jface.databinding.viewers.ViewersObservables; import org.eclipse.jface.layout.TreeColumnLayout; import org.eclipse.jface.viewers.ColumnWeightData; import org.eclipse.jface.viewers.ISelectionChangedListener; 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.TreeViewer; import org.eclipse.jface.viewers.Viewer; import org.eclipse.jface.viewers.ViewerComparator; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Font; import org.eclipse.swt.graphics.FontData; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Group; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Menu; import org.eclipse.swt.widgets.ProgressBar; import org.eclipse.swt.widgets.Tree; import org.eclipse.swt.widgets.TreeColumn; import org.eclipse.swt.widgets.TreeItem; import com.amazonaws.eclipse.dynamodb.DynamoDBPlugin; import com.amazonaws.eclipse.dynamodb.testtool.TestToolVersion.InstallState; /** * A table that displays the set of available DynamoDB Local Test Tool * versions and allows you to install or uninstall them. */ public class TestToolVersionTable extends Composite { /** * Background job that checks for changes to the list of test tool * versions and updates the table. */ private final Job updateJob = new Job("DynamoDB Local Test Tool Update") { @Override protected IStatus run(final IProgressMonitor monitor) { setInput(TestToolManager.INSTANCE.getAllVersions()); return Status.OK_STATUS; } }; /** * Background job that installs a particular version of the test tool. */ private class InstallJob extends Job { private final TestToolVersion version; /** * @param version The version of the test tool to install. */ public InstallJob(final TestToolVersion version) { super("DynamoDB Local Test Tool Install"); this.version = version; } /** {@inheritDoc} */ @Override protected IStatus run(final IProgressMonitor monitor) { // Mark the version as currently being installed so we disable // the 'install' button. TestToolManager.INSTANCE.markInstalling(version); checkForUpdates(); // Actually do the install, updating the progress bar as we go. try { TestToolManager.INSTANCE .installVersion(version, progressMonitor); } finally { checkForUpdates(); } return Status.OK_STATUS; } } /** * A background job that uninstalls a particular version of the test tool. */ private class UninstallJob extends Job { private final TestToolVersion version; /** * @param version The version of the test tool to uninstall. */ public UninstallJob(final TestToolVersion version) { super("DynamoDB Local Test Tool Uninstall"); this.version = version; } /** {@inheritDoc} */ @Override protected IStatus run(final IProgressMonitor monitor) { TestToolManager.INSTANCE.uninstallVersion(version); checkForUpdates(); return Status.OK_STATUS; } } private final Collection<Font> fonts = new LinkedList<Font>(); private TreeViewer viewer; private ToolBarManager toolbar; private CustomProgressMonitor progressMonitor; private Group detailsGroup; private Label versionText; private Label descriptionText; private Action installAction; private Action uninstallAction; /** * @param parent The parent composite to attach to. */ public TestToolVersionTable(final Composite parent) { super(parent, SWT.NONE); GridLayout layout = new GridLayout(1, false); setLayout(layout); createToolbar(); createViewer(); createDetailsGroup(); createActions(); hookToolbar(); hookContextMenu(); checkForUpdates(); } /** * @return The currently selected test tool version (or null). */ public TestToolVersion getSelection() { StructuredSelection selection = (StructuredSelection) viewer.getSelection(); return (TestToolVersion) selection.getFirstElement(); } /** * @return an observable that tracks the current selection */ public IObservableValue getObservableSelection() { return ViewersObservables.observeSingleSelection(viewer); } /** {@inheritDoc} */ @Override public void dispose() { for (Font font : fonts) { font.dispose(); } super.dispose(); } /** * Create the toolbar, with install and uninstall buttons and an initially * hidden progress bar to track install status. */ private void createToolbar() { // Bind to the top of wherever we end up. Composite wrapper = new Composite(this, SWT.NONE); wrapper.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false)); wrapper.setLayout(new GridLayout(2, false)); toolbar = new CustomToolBarManager(); // Toolbar on the left. Control control = toolbar.createControl(wrapper); control.setLayoutData( new GridData(SWT.LEFT, SWT.CENTER, false, false) ); // Progress bar on the right. ProgressBar progressBar = new ProgressBar(wrapper, SWT.NONE); progressBar.setLayoutData( new GridData(SWT.RIGHT, SWT.CENTER, true, false) ); progressBar.setVisible(false); progressMonitor = new CustomProgressMonitor(progressBar); } /** * Create the actual table view displaying the list of selectable test * tool versions. */ private void createViewer() { // Grab any leftover space in the middle. Composite wrapper = new Composite(this, SWT.NONE); GridData data = new GridData(SWT.FILL, SWT.FILL, true, false); data.heightHint = 200; wrapper.setLayoutData(data); TreeColumnLayout layout = new TreeColumnLayout(); wrapper.setLayout(layout); viewer = new TreeViewer( wrapper, SWT.H_SCROLL | SWT.V_SCROLL | SWT.FULL_SELECTION | SWT.BORDER | SWT.SINGLE ); TestToolVersionProvider provider = new TestToolVersionProvider(); viewer.setContentProvider(provider); viewer.setLabelProvider(provider); viewer.setComparator(new VersionComparator()); viewer.addSelectionChangedListener(new ISelectionChangedListener() { public void selectionChanged(final SelectionChangedEvent event) { onSelectionChanged(); } }); Tree tree = viewer.getTree(); tree.setHeaderVisible(true); TreeColumn versionColumn = new TreeColumn(tree, SWT.LEFT); versionColumn.setText("Version"); layout.setColumnData(versionColumn, new ColumnWeightData(75)); TreeColumn installColumn = new TreeColumn(tree, SWT.CENTER); installColumn.setText("Installed?"); layout.setColumnData(installColumn, new ColumnWeightData(25)); } /** * Create the (initially hidden) details group, which shows a longer text * description of the currently-selected version. */ private void createDetailsGroup() { detailsGroup = new Group(this, SWT.NONE); detailsGroup.setText("Details"); detailsGroup.setLayout(new GridLayout(2, false)); detailsGroup.setVisible(false); // Pin to the bottom to allow the table control to have as much space // as it wants. GridData data = new GridData(SWT.FILL, SWT.BOTTOM, true, false); data.widthHint = 300; detailsGroup.setLayoutData(data); Label version = new Label(detailsGroup, SWT.TOP); version.setText("Version: "); makeBold(version); versionText = new Label(detailsGroup, SWT.WRAP); Label description = new Label(detailsGroup, SWT.TOP); description.setText("Description: "); makeBold(description); descriptionText = new Label(detailsGroup, SWT.WRAP); data = new GridData(SWT.FILL, SWT.FILL, true, true); data.widthHint = 300; descriptionText.setLayoutData(data); } /** * Make the given label bold. * * @param label The label to embolden. */ private void makeBold(final Label label) { FontData[] fontData = label.getFont().getFontData(); fontData[0].setStyle(SWT.BOLD); Font font = new Font(getDisplay(), fontData[0]); fonts.add(font); label.setFont(font); } /** * Create the install and uninstall actions for the toolbar and context * menu. */ private void createActions() { installAction = new Action() { @Override public void run() { new InstallJob(getSelection()).schedule(); } }; installAction.setText("Install"); installAction.setToolTipText("Install the selected version."); installAction.setImageDescriptor( DynamoDBPlugin.getDefault() .getImageRegistry() .getDescriptor("add") ); installAction.setEnabled(false); uninstallAction = new Action() { @Override public void run() { new UninstallJob(getSelection()).schedule(); } }; uninstallAction.setText("Uninstall"); uninstallAction.setToolTipText("Uninstall the selected version."); uninstallAction.setImageDescriptor( DynamoDBPlugin.getDefault() .getImageRegistry() .getDescriptor("remove") ); uninstallAction.setEnabled(false); } /** * Hook the actions created above into the toolbar. */ private void hookToolbar() { toolbar.add(installAction); toolbar.add(new Separator()); toolbar.add(uninstallAction); toolbar.update(true); } /** * Hook the actions created above into the context menu for the table. */ private void hookContextMenu() { MenuManager manager = new MenuManager("#PopupMenu"); manager.setRemoveAllWhenShown(true); manager.addMenuListener(new IMenuListener() { public void menuAboutToShow(IMenuManager manager) { manager.add(installAction); manager.add(uninstallAction); } }); Menu menu = manager.createContextMenu(viewer.getControl()); viewer.getControl().setMenu(menu); manager.createContextMenu(viewer.getControl()); } /** * Check for changes to the list of test tool versions. */ private void checkForUpdates() { updateJob.schedule(); } /** * Handler callback for when the selected version changes. Enable or * disable the install/uninstall actions as appropriate, and update the * details group. */ private void onSelectionChanged() { TestToolVersion version = getSelection(); if (version == null) { installAction.setEnabled(false); uninstallAction.setEnabled(false); detailsGroup.setVisible(false); } else { installAction.setEnabled( version.getInstallState() == InstallState.NOT_INSTALLED ); uninstallAction.setEnabled( version.getInstallState() == InstallState.INSTALLED ); versionText.setText(version.getName()); descriptionText.setText(version.getDescription()); detailsGroup.setVisible(true); detailsGroup.pack(); } toolbar.update(false); } /** * Set the list of test tool versions to display in the table. * * @param versions The list of versions. */ private void setInput(final List<TestToolVersion> versions) { Display.getDefault().asyncExec(new Runnable() { public void run() { _setInput(versions); } }); } /** * Helper for setInput() that MUST be run on the UI thread. */ private void _setInput(final List<TestToolVersion> versions) { TestToolVersion previous = getSelection(); viewer.setInput(versions); if (previous != null) { Tree tree = viewer.getTree(); for (int i = 0; i < tree.getItemCount(); ++i) { TreeItem item = tree.getItem(i); TestToolVersion version = (TestToolVersion) item.getData(); if (previous.getName().equals(version.getName())) { tree.select(item); tree.notifyListeners(SWT.Selection, null); break; } } } } /** * A content and label provider for the test tool table. */ private class TestToolVersionProvider extends LabelProvider implements ITreeContentProvider, ITableLabelProvider { private List<TestToolVersion> versions; /** {@inheritDoc} */ public Object[] getElements(final Object inputElement) { if (versions == null) { return null; } return versions.toArray(); } /** {@inheritDoc} */ @SuppressWarnings("unchecked") public void inputChanged(final Viewer viewer, final Object oldInput, final Object newInput) { versions = (List<TestToolVersion>) newInput; } /** {@inheritDoc} */ public Image getColumnImage(final Object element, final int columnIndex) { return null; } /** {@inheritDoc} */ public String getColumnText(final Object element, final int columnIndex) { if (!(element instanceof TestToolVersion)) { return "???"; } TestToolVersion version = (TestToolVersion) element; switch (columnIndex) { case 0: return version.getName(); case 1: if (version.isRunning()) { return "Running..."; } if (version.isInstalled()) { return "Yes"; } if (version.isInstalling()) { return "Installing..."; } return ""; default: return "???"; } } /** {@inheritDoc} */ public Object[] getChildren(final Object parentElement) { return new Object[0]; } /** {@inheritDoc} */ public Object getParent(final Object element) { return null; } /** {@inheritDoc} */ public boolean hasChildren(final Object element) { return false; } } /** * Sort versions in reverse alphabetical order (which, since version * numbers are dates, is also conveniently reverse chronological order). */ private class VersionComparator extends ViewerComparator { /** {@inheritDoc} */ @Override public int compare(final Viewer viewer, final Object e1, final Object e2) { if (!(e1 instanceof TestToolVersion)) { return 0; } if (!(e2 instanceof TestToolVersion)) { return 0; } TestToolVersion v1 = (TestToolVersion) e1; TestToolVersion v2 = (TestToolVersion) e2; return v2.getName().compareTo(v1.getName()); } } /** * A Progress Monitor that updates a ProgressBar as the test tool is * installed. */ private static class CustomProgressMonitor implements IProgressMonitor { private final ProgressBar progressBar; /** * @param progressBar The progress bar to update. */ public CustomProgressMonitor(final ProgressBar progressBar) { this.progressBar = progressBar; } /** {@inheritDoc} */ public void beginTask(final String name, final int totalWork) { Display.getDefault().asyncExec(new Runnable() { public void run() { progressBar.setMinimum(0); progressBar.setMaximum(totalWork); progressBar.setSelection(0); progressBar.setToolTipText(name); progressBar.setVisible(true); } }); } /** {@inheritDoc} */ public void worked(final int work) { Display.getDefault().asyncExec(new Runnable() { public void run() { progressBar.setSelection( progressBar.getSelection() + work ); } }); } /** {@inheritDoc} */ public void done() { Display.getDefault().asyncExec(new Runnable() { public void run() { progressBar.setVisible(false); } }); } /** {@inheritDoc} */ public void internalWorked(double arg0) { throw new UnsupportedOperationException(); } /** {@inheritDoc} */ public boolean isCanceled() { return false; } /** {@inheritDoc} */ public void setCanceled(boolean arg0) { throw new UnsupportedOperationException(); } /** {@inheritDoc} */ public void setTaskName(String arg0) { throw new UnsupportedOperationException(); } /** {@inheritDoc} */ public void subTask(String arg0) { throw new UnsupportedOperationException(); } } /** * A toolbar manager that forces text mode on all actions added to it. */ private static class CustomToolBarManager extends ToolBarManager { public CustomToolBarManager() { super(SWT.FLAT | SWT.RIGHT); } /** {@inheritDoc} */ @Override public void add(final IAction action) { ActionContributionItem item = new ActionContributionItem(action); item.setMode(ActionContributionItem.MODE_FORCE_TEXT); super.add(item); } } }