/******************************************************************************* * Copyright (c) 2014, 2015 Pivotal Software, Inc. * * All rights reserved. This program and the accompanying materials * are made available under the terms of 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://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License 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. * * Contributors: * Pivotal Software, Inc. - initial API and implementation ********************************************************************************/ package org.cloudfoundry.ide.eclipse.server.ui.internal; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import org.cloudfoundry.client.lib.domain.CloudRoute; import org.cloudfoundry.ide.eclipse.server.core.internal.CloudFoundryPlugin; import org.cloudfoundry.ide.eclipse.server.ui.internal.wizards.CloudUIEvent; import org.eclipse.core.runtime.Status; import org.eclipse.jface.layout.GridDataFactory; import org.eclipse.jface.layout.GridLayoutFactory; import org.eclipse.jface.viewers.CheckboxTableViewer; import org.eclipse.jface.viewers.IStructuredContentProvider; import org.eclipse.jface.viewers.ITableLabelProvider; import org.eclipse.jface.viewers.LabelProvider; import org.eclipse.jface.viewers.TableViewer; import org.eclipse.jface.viewers.Viewer; import org.eclipse.jface.viewers.ViewerFilter; import org.eclipse.jface.viewers.ViewerSorter; import org.eclipse.osgi.util.NLS; import org.eclipse.swt.SWT; import org.eclipse.swt.accessibility.AccessibleAdapter; import org.eclipse.swt.accessibility.AccessibleEvent; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Table; import org.eclipse.swt.widgets.TableColumn; import org.eclipse.swt.widgets.TableItem; import org.eclipse.swt.widgets.Widget; /** * Allows Cloud routes to be viewed and edited. */ public class CloudRoutePart extends UIPart { public static IEventSource<?> ROUTES_REMOVED = new CloudUIEvent(Messages.CloudRoutePart_TEXT_ROUTES_REMOVED); private CheckboxTableViewer viewer; private Button showInUseButton; private Button showRemovedRoutesButton; private Button removeButton; private static final String IN_USE = "x"; //$NON-NLS-1$ private List<CloudRoute> routesToRemove = new ArrayList<CloudRoute>(); private List<CloudRoute> allRoutes = new ArrayList<CloudRoute>(); private final Color DISABLED = Display.getDefault().getSystemColor(SWT.COLOR_GRAY); protected enum RouteColumn { NAME(Messages.COMMONTXT_NAME, 250), DOMAIN(Messages.CloudRoutePart_TEXT_ROUTE_DOMAIN, 100), IN_USE(Messages.CloudRoutePart_TEXT_ROUTE_INUSE, 30); private String name; private int width; private RouteColumn(String name, int width) { this.name = name; this.width = width; } public String getName() { return name; } public int getWidth() { return width; } } public CloudRoutePart() { } public TableViewer getViewer() { return viewer; } public List<CloudRoute> getRoutesToDelete() { return routesToRemove; } public void setInput(List<CloudRoute> routes) { if (routes == null || routes.isEmpty()) { notifyStatusChange(CloudFoundryPlugin.getErrorStatus(Messages.CloudRoutePart_ERROR_NO_ROUTE_AVAIL)); return; } this.allRoutes = new ArrayList<CloudRoute>(routes); viewer.setInput(routes); refreshAll(); } public Control createPart(Composite parent) { Composite generalArea = new Composite(parent, SWT.NONE); GridLayoutFactory.fillDefaults().numColumns(1).applyTo(generalArea); GridDataFactory.fillDefaults().grab(true, true).applyTo(generalArea); createTableButtonArea(generalArea); createFilterButtons(generalArea); return generalArea; } protected void createFilterButtons(Composite parent) { showInUseButton = new Button(parent, SWT.CHECK); showInUseButton.setText(Messages.ROUTES_SHOW_IN_USE); GridDataFactory.fillDefaults().grab(false, false).applyTo(showInUseButton); showInUseButton.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { refreshAll(); } }); showRemovedRoutesButton = new Button(parent, SWT.CHECK); showRemovedRoutesButton.setText(Messages.ROUTES_SHOW_REMOVED); GridDataFactory.fillDefaults().grab(false, false).applyTo(showRemovedRoutesButton); showRemovedRoutesButton.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { refreshAll(); String removeButtonLabel = showRemovedRoutesButton.getSelection() ? Messages.CloudRoutePart_UNDO : Messages.COMMONTXT_REMOVE; removeButton.setText(removeButtonLabel); } }); } protected List<CloudRoute> getAllUnused() { List<CloudRoute> unused = new ArrayList<CloudRoute>(); for (CloudRoute route : allRoutes) { if (!route.inUse()) { unused.add(route); } } return unused; } protected void createSelectionButtonArea(Composite parent) { Composite buttons = new Composite(parent, SWT.NONE); GridLayoutFactory.fillDefaults().numColumns(1).applyTo(buttons); GridDataFactory.fillDefaults().grab(false, false).applyTo(buttons); Button selectAll = new Button(buttons, SWT.PUSH); selectAll.setText(Messages.SELECT_ALL); GridDataFactory.fillDefaults().grab(false, false).applyTo(selectAll); selectAll.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { viewer.setCheckedElements(getAllUnused().toArray(new CloudRoute[0])); } }); Button deselectAll = new Button(buttons, SWT.PUSH); deselectAll.setText(Messages.DESELECT_ALL); GridDataFactory.fillDefaults().grab(false, false).applyTo(selectAll); deselectAll.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { viewer.setCheckedElements(Collections.emptyList().toArray(new CloudRoute[0])); } }); removeButton = new Button(buttons, SWT.PUSH); removeButton.setText(Messages.COMMONTXT_REMOVE); GridDataFactory.fillDefaults().grab(false, false).applyTo(removeButton); removeButton.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { // The remove button has dual purpose: // 1. if viewing list of available routes, any selection will // remove them // 2. if viewing list of removed routes, any selection will undo // the removed routes Object[] selectedRoutes = viewer.getCheckedElements(); if (selectedRoutes == null) { selectedRoutes = new CloudRoute[0]; } List<CloudRoute> toRemove = new ArrayList<CloudRoute>(routesToRemove); if (!showRemovedRoutesButton.getSelection()) { for (Object obj : selectedRoutes) { if (!toRemove.contains(obj)) { toRemove.add((CloudRoute) obj); } } } else { // Undo those that are checked by removing them from the // list of routes to delete for (Object obj : selectedRoutes) { toRemove.remove(obj); } } remove(toRemove); } }); } protected void createTableButtonArea(Composite parent) { Composite buttonsAndViewer = new Composite(parent, SWT.NONE); GridLayoutFactory.fillDefaults().numColumns(2).applyTo(buttonsAndViewer); GridDataFactory.fillDefaults().grab(true, true).applyTo(buttonsAndViewer); Composite tableArea = new Composite(buttonsAndViewer, SWT.NONE); GridLayoutFactory.fillDefaults().numColumns(1).applyTo(tableArea); GridDataFactory.fillDefaults().grab(true, true).applyTo(tableArea); createSelectionButtonArea(buttonsAndViewer); final Table table = new Table(tableArea, SWT.BORDER | SWT.CHECK); GridDataFactory.fillDefaults().grab(true, true).applyTo(table); viewer = new CheckboxTableViewer(table); viewer.setContentProvider(new RoutesContentProvider()); viewer.setLabelProvider(new TreeLabelProvider(viewer)); viewer.setSorter(new ViewerSorter() { @Override public int compare(Viewer viewer, Object e1, Object e2) { TableColumn sortColumn = table.getSortColumn(); if (sortColumn != null) { RouteColumn column = (RouteColumn) sortColumn.getData(); int result = 0; int sortDirection = table.getSortDirection(); if (column != null) { if (e1 instanceof CloudRoute && e2 instanceof CloudRoute) { CloudRoute rt1 = (CloudRoute) e1; CloudRoute rt2 = (CloudRoute) e2; switch (column) { case NAME: result = rt1.getName().toLowerCase().compareTo(rt2.getName().toLowerCase()); break; case DOMAIN: result = rt1.getDomain().getName().compareTo(rt2.getDomain().getName()); break; case IN_USE: if (rt1.inUse() && !rt2.inUse()) { result = -1; } else if (rt2.inUse() && !rt1.inUse()) { result = 1; } break; } } } return sortDirection == SWT.UP ? result : -result; } return super.compare(viewer, e1, e2); } }); viewer.addFilter(new ViewerFilter() { @Override public boolean select(Viewer viewer, Object items, Object item) { if (item instanceof CloudRoute) { CloudRoute route = (CloudRoute) item; return route.inUse() ? showInUseButton.getSelection() : (showRemovedRoutesButton.getSelection() && routesToRemove.contains(route)) || (!showRemovedRoutesButton.getSelection() && !routesToRemove.contains(route)); } return false; } }); viewer.getTable().addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent event) { Widget item = event.item; // Disable selecting routes that are in use if (event.detail == SWT.CHECK && item instanceof TableItem) { TableItem tableItem = (TableItem) item; Object element = tableItem.getData(); if (element instanceof CloudRoute && ((CloudRoute) element).inUse()) { event.doit = false; event.detail = SWT.NONE; tableItem.setChecked(false); } } } }); addColumns(viewer); new TableResizeHelper(viewer).enableResizing(); // Add accessibility listener to the table, so screen reader can provide a good description // of what is selected (in this case, a cloud route and its value) viewer.getControl().getAccessible ().addAccessibleListener (new AccessibleAdapter() { @Override public void getName (AccessibleEvent e) { if (e.childID >= 0) { if (e.result != null) { e.result = NLS.bind(Messages.CloudRoutePart_TEXT_TABLE_ACC_LABEL, e.result); } else { e.result = Messages.CloudFoundryApplicationServicesWizardPage_TEXT_SERVICE_SELECTION; } } } }); } public void addColumns(final TableViewer tableViewer) { final Table table = tableViewer.getTable(); table.setHeaderVisible(true); int columnIndex = 0; List<String> columnProperties = new ArrayList<String>(); TableColumn sortColumn = null; for (RouteColumn clm : RouteColumn.values()) { TableColumn tableColumn = new TableColumn(table, SWT.NONE, columnIndex++); tableColumn.setText(clm.getName()); tableColumn.setWidth(clm.getWidth()); tableColumn.setData(clm); columnProperties.add(clm.getName()); tableColumn.addSelectionListener(new ColumnSortListener(tableViewer) { protected void refresh() { refreshAll(); } }); if (sortColumn == null) { sortColumn = tableColumn; } } if (sortColumn != null) { table.setSortColumn(sortColumn); table.setSortDirection(SWT.UP); } tableViewer.setColumnProperties(columnProperties.toArray(new String[0])); } protected class TreeLabelProvider extends LabelProvider implements ITableLabelProvider { private final TableViewer viewer; public TreeLabelProvider(TableViewer viewer) { this.viewer = viewer; } @Override public Image getImage(Object element) { return null; } @Override public String getText(Object element) { if (element instanceof CloudRoute) { return ((CloudRoute) element).getName(); } return super.getText(element); } public Image getColumnImage(Object element, int columnIndex) { return null; } public String getColumnText(Object element, int columnIndex) { String result = null; TableColumn column = viewer.getTable().getColumn(columnIndex); if (column != null) { switch (columnIndex) { case 0: result = getText(element); break; case 1: if (element instanceof CloudRoute) { result = ((CloudRoute) element).getDomain().getName(); } break; case 2: if (element instanceof CloudRoute && ((CloudRoute) element).inUse()) { return IN_USE; } break; } } return result; } } /** * * @param updatedToRemove elements to remove. Passing a null or empty list * will clear the list of items to remove. */ protected void remove(List<?> updatedToRemove) { // If no change, then do nothing to avoid firing change events if (routesToRemove.equals(updatedToRemove)) { return; } routesToRemove.clear(); if (updatedToRemove != null) { for (Object obj : updatedToRemove) { if (obj instanceof CloudRoute) { CloudRoute route = (CloudRoute) obj; if (!routesToRemove.contains(route)) { routesToRemove.add(route); } } } } notifyChange(new PartChangeEvent(routesToRemove, Status.OK_STATUS, ROUTES_REMOVED)); refreshAll(); } protected void refreshAll() { viewer.refresh(true); if (showInUseButton.getSelection()) { TableItem[] items = viewer.getTable().getItems(); for (TableItem item : items) { CloudRoute route = (CloudRoute) item.getData(); if (route.inUse()) { item.setForeground(DISABLED); } } } } protected class RoutesContentProvider implements IStructuredContentProvider { public void dispose() { } public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { } public Object[] getElements(Object inputElement) { if (inputElement instanceof Collection) { return ((Collection<?>) inputElement).toArray(new Object[0]); } return null; } } }