/*******************************************************************************
* Copyright (c) 2014 Pivotal Software Inc and IBM Corporation.
*
* 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
* IBM Corporation - Additions to services wizard
*******************************************************************************/
package org.cloudfoundry.ide.eclipse.server.ui.internal.wizards;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.cloudfoundry.client.lib.domain.CloudServiceOffering;
import org.cloudfoundry.client.lib.domain.CloudServicePlan;
import org.cloudfoundry.ide.eclipse.server.core.internal.CloudFoundryServer;
import org.cloudfoundry.ide.eclipse.server.core.internal.client.LocalCloudService;
import org.cloudfoundry.ide.eclipse.server.ui.ICloudFoundryServiceWizardIconProvider;
import org.cloudfoundry.ide.eclipse.server.ui.internal.CloudFoundryImages;
import org.cloudfoundry.ide.eclipse.server.ui.internal.Messages;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.IMessageProvider;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.wizard.WizardPage;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.ScrolledComposite;
import org.eclipse.swt.events.ControlAdapter;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.FocusListener;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.events.MouseWheelListener;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.events.TraverseEvent;
import org.eclipse.swt.events.TraverseListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.FontMetrics;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
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.Layout;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.forms.widgets.Hyperlink;
import org.eclipse.ui.statushandlers.StatusManager;
import org.eclipse.wst.server.core.IRuntime;
public class CloudFoundryServiceWizardPage1 extends WizardPage {
protected static final String PAGE_NAME = CloudFoundryServiceWizardPage1.class.getName();
protected ScrolledComposite scrollComp;
protected String filter;
protected Font boldFont;
protected Color selEdgeColor;
protected Color selFillColor;
protected Text nameText;
protected boolean updating = false;
private List<CloudServiceOffering> serviceOfferings;
private List<CFServiceWizUI> allServicesList = new ArrayList<CFServiceWizUI>();
private List<CFServiceWizUI> selectedServicesList = new ArrayList<CFServiceWizUI>();
CFWizServicePage1Validation servicePageValidation;
CloudFoundryServer cloudServer;
protected Group serviceInfoComposite;
public LocalCloudService getService() {
List<CFServiceWizUI> selectedServices = getSelectedList();
if (selectedServices != null && selectedServices.size() == 1) {
return selectedServices.get(0).convertToLocalCloudService();
}
return null;
}
public static final String INSTALL_SERVICE_TEXT = Messages.CloudFoundryServiceWizardPage1_TEXT_SELECT_SERVICE;
public static final String DEFAULT_FILTER_TEXT = Messages.CloudFoundryServiceWizardPage1_TEXT_TYPE_FILTER;
public static final String REMOVE_SERVICE_TEXT = Messages.COMMONTXT_REMOVE;
public static final String ADD_SERVICE_TEXT = Messages.CloudFoundryServiceWizardPage1_TEXT_SELECT;
public static final String DESCRIPTION_FINAL = Messages.CloudFoundryServiceWizardPage1_TEXT_FINISH;
public static final String FILTER_TEXT = Messages.CloudFoundryServiceWizardPage1_TEXT_FILTER;
private Composite layoutList;
private ServiceListTraverseListener traverseListener;
private Text filterText;
private Text serviceNameText;
/** Optional field -- used to provide icons in service wizard if available */
CFServiceWizardDynamicIconLoader loader;
private FontMetrics fontMetrics;
public CloudFoundryServiceWizardPage1(CloudFoundryServer cloudServer) {
super(PAGE_NAME);
this.cloudServer = cloudServer;
setTitle(Messages.CloudFoundryServiceWizardPage1_TTILE_SERVICE_CONFIG);
setDescription(DESCRIPTION_FINAL);
ImageDescriptor banner = CloudFoundryImages.getWizardBanner(cloudServer.getServer().getServerType().getId());
if (banner != null) {
setImageDescriptor(banner);
}
this.cloudServer = cloudServer;
// Is there an icon provide for the service, if so, use it
IRuntime runtime = cloudServer.getServer().getRuntime();
String runtimeTypeId = runtime.getRuntimeType().getId();
ICloudFoundryServiceWizardIconProvider provider = CloudFoundryServiceWizardIconProviderRegistry.getInstance()
.getIconProvider(runtimeTypeId);
if (provider != null) {
loader = new CFServiceWizardDynamicIconLoader(provider, this.cloudServer);
loader.start();
}
servicePageValidation = new CFWizServicePage1Validation(this);
setPageComplete(false);
traverseListener = new ServiceListTraverseListener();
}
private static void sortServicePlans(List<CloudServiceOffering> configurations) {
for (CloudServiceOffering offering : configurations) {
Collections.sort(offering.getCloudServicePlans(), new Comparator<CloudServicePlan>() {
public int compare(CloudServicePlan o1, CloudServicePlan o2) {
return o1.getName().compareTo(o2.getName());
}
});
}
}
private boolean updateConfiguration() {
try {
getContainer().run(true, false, new IRunnableWithProgress() {
public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
try {
serviceOfferings = cloudServer.getBehaviour().getServiceOfferings(monitor);
Collections.sort(serviceOfferings, new Comparator<CloudServiceOffering>() {
public int compare(CloudServiceOffering o1, CloudServiceOffering o2) {
return o1.getDescription().compareTo(o2.getDescription());
}
});
// Pivotal Tracker [77602464] - Leaving plans in order
// received by server keeps them in pricing order from
// low to high
// sortServicePlans(serviceOfferings);
}
catch (CoreException e) {
throw new InvocationTargetException(e);
}
catch (OperationCanceledException e) {
throw new InterruptedException();
}
finally {
monitor.done();
}
}
});
if (serviceOfferings != null) {
for (CloudServiceOffering o : serviceOfferings) {
if (!allServicesList.contains(new CFServiceWizUI(o))) {
allServicesList.add(new CFServiceWizUI(o));
}
}
}
return true;
}
catch (InvocationTargetException e) {
IStatus status = cloudServer.error(
NLS.bind(Messages.CloudFoundryServiceWizardPage1_ERROR_CONFIG_RETRIVE, e.getCause().getMessage()), e);
StatusManager.getManager().handle(status, StatusManager.LOG);
setMessage(status.getMessage(), IMessageProvider.ERROR);
}
catch (InterruptedException e) {
// ignore
}
return false;
}
Composite topComp;
public void createControl(Composite parent) {
Composite comp = new Composite(parent, SWT.NONE);
topComp = comp;
GridLayout layout = new GridLayout();
layout.horizontalSpacing = convertHorizontalDLUsToPixels(comp, 4);
layout.verticalSpacing = convertVerticalDLUsToPixels(comp, 4);
layout.numColumns = 3;
comp.setLayout(layout);
GridData data;
Composite filterComp = new Composite(comp, SWT.NONE);
layout = new GridLayout();
layout.numColumns = 3;
layout.marginHeight = 0;
layout.marginWidth = 0;
layout.horizontalSpacing = convertHorizontalDLUsToPixels(comp, 4);
layout.verticalSpacing = convertHorizontalDLUsToPixels(comp, 4);
filterComp.setLayout(layout);
data = new GridData(SWT.FILL, SWT.BEGINNING, true, false);
data.horizontalSpan = 3;
data.horizontalIndent = 0;
data.verticalIndent = 0;
filterComp.setLayoutData(data);
final Label filterLabel = new Label(filterComp, SWT.NONE);
filterLabel.setText(FILTER_TEXT);
data = new GridData(GridData.BEGINNING, GridData.CENTER, false, false);
filterLabel.setLayoutData(data);
filterText = new Text(filterComp, SWT.SEARCH);
filterText.setLayoutData(new GridData(GridData.FILL, GridData.CENTER, true, false, 2, 1));
filterText.setText(DEFAULT_FILTER_TEXT);
scrollComp = new ScrolledComposite(comp, SWT.BORDER | SWT.V_SCROLL);
data = new GridData(GridData.FILL, GridData.FILL, true, true, 3, 1);
data.heightHint = 300;
scrollComp.setLayoutData(data);
scrollComp.setAlwaysShowScrollBars(true);
scrollComp.setBackground(scrollComp.getDisplay().getSystemColor(SWT.COLOR_LIST_BACKGROUND));
int lineHeight = filterText.computeSize(SWT.DEFAULT, SWT.DEFAULT).y;
scrollComp.getVerticalBar().setPageIncrement(lineHeight * 3);
scrollComp.getVerticalBar().setIncrement(lineHeight);
scrollComp.addControlListener(new ControlAdapter() {
public void controlResized(ControlEvent event) {
Control c = scrollComp.getContent();
if (c == null) {
return;
}
Rectangle r = scrollComp.getClientArea();
r.height = c.computeSize(r.width, SWT.DEFAULT).y;
c.setBounds(r);
}
});
layoutList = createLayoutList(scrollComp);
filterText.addModifyListener(new ModifyListener() {
public void modifyText(ModifyEvent event) {
if (updating) {
return;
}
String text = filterText.getText();
if (DEFAULT_FILTER_TEXT.equals(text)) {
filter = null;
}
else {
filter = filterText.getText();
}
updateServicesTable();
}
});
filterText.addFocusListener(new FocusListener() {
public void focusLost(FocusEvent e) {
if (filterText.getText().isEmpty()) {
updating = true;
filterText.setText(DEFAULT_FILTER_TEXT);
updating = false;
}
}
public void focusGained(FocusEvent e) {
if (DEFAULT_FILTER_TEXT.equals(filterText.getText())) {
updating = true;
filterText.setText(""); //$NON-NLS-1$
updating = false;
}
}
});
filterText.addTraverseListener(traverseListener);
serviceInfoComposite = new Group(comp, SWT.NONE);
serviceInfoComposite.setText(Messages.CloudFoundryServiceWizardPage1_TEXT_SET_NAME_PLAN);
data = new GridData(GridData.FILL, GridData.FILL, true, false, 3, 1);
data.verticalIndent = 5;
serviceInfoComposite.setLayoutData(data);
layout = new GridLayout();
layout.numColumns = 2;
layout.marginHeight = 5;
layout.marginWidth = 5;
serviceInfoComposite.setLayout(layout);
serviceInfoComposite.setVisible(false);
servicePageValidation.updatePageState();
Dialog.applyDialogFont(comp);
setControl(comp);
MouseWheelListener mwl = new MouseWheelListener() {
public void mouseScrolled(MouseEvent event) {
scrollComp.setFocus();
}
};
filterText.addMouseWheelListener(mwl);
// nameText.addMouseWheelListener(mwl);
}
private Composite createLayoutList(Composite parent) {
Composite result = new Composite(parent, SWT.NONE);
result.setBackground(parent.getDisplay().getSystemColor(SWT.COLOR_LIST_BACKGROUND));
Display display = result.getDisplay();
Font font = result.getFont();
if (boldFont == null) {
FontData[] fontData = font.getFontData();
fontData[0].setStyle(SWT.BOLD);
fontData[0].setHeight(fontData[0].getHeight());
boldFont = new Font(display, fontData);
Color c1 = display.getSystemColor(SWT.COLOR_LIST_SELECTION);
Color c2 = display.getSystemColor(SWT.COLOR_LIST_BACKGROUND);
selEdgeColor = new Color(display, (c1.getRed() + c2.getRed() * 3) / 4,
(c1.getGreen() + c2.getGreen() * 3) / 4, (c1.getBlue() + c2.getBlue() * 3) / 4);
selFillColor = new Color(display, (c1.getRed() + c2.getRed() * 8) / 9,
(c1.getGreen() + c2.getGreen() * 8) / 9, (c1.getBlue() + c2.getBlue() * 8) / 9);
result.addDisposeListener(new DisposeListener() {
public void widgetDisposed(DisposeEvent event) {
if (boldFont != null && !boldFont.isDisposed()) {
boldFont.dispose();
}
if (selEdgeColor != null && !selEdgeColor.isDisposed()) {
selEdgeColor.dispose();
}
if (selFillColor != null && !selFillColor.isDisposed()) {
selFillColor.dispose();
}
}
});
}
result.addPaintListener(new PaintListener() {
public void paintControl(PaintEvent event) {
if(selectedServicesList != null && selectedServicesList.size() > 0 && selectedServicesList.get(0).getNameLabel().isVisible()) {
Rectangle r = selectedServicesList.get(0).getAppxLocation();
if(r != null) {
Rectangle r2 = new Rectangle(r.x, r.y, r.width, r.height);
r2.x -= 3;
r2.width += 6;
r2.y -= 3;
r2.height += 6;
event.gc.setBackground(selFillColor);
event.gc.fillRoundRectangle(r2.x, r2.y, r2.width, r2.height, 7, 7);
event.gc.setForeground(selEdgeColor);
event.gc.drawRoundRectangle(r2.x, r2.y, r2.width, r2.height, 7, 7);
}
}
}
});
Color color = result.getBackground();
updateConfiguration();
Control[] tabList = new Control[allServicesList.size()];
for(int x = 0; x < allServicesList.size(); x++) {
CFServiceWizUI service = allServicesList.get(x);
final Label imgLabel = new Label(result, SWT.NONE);
imgLabel.setBackground(color);
imgLabel.setData(service);
final Label nameLabel = new Label(result, SWT.WRAP);
nameLabel.setData(service);
nameLabel.setFont(boldFont);
nameLabel.setBackground(color);
nameLabel.setText(service.getName());
tabList[x] = nameLabel;
final Label descLabel = new Label(result, SWT.WRAP);
descLabel.setData(service);
descLabel.setBackground(color);
String desc = service.getDescription();
descLabel.setText(trimTextAtBound(desc, 200));
service.setDescriptionLabel(descLabel);
service.setImageLabel(imgLabel);
service.setNameLabel(nameLabel);
imgLabel.addMouseListener(new CFWizSelectItemListener(service));
nameLabel.addMouseListener(new CFWizSelectItemListener(service));
descLabel.addMouseListener(new CFWizSelectItemListener(service));
descLabel.setToolTipText(wrapAndTrimTextAtBound(desc, 100));
}
result.addMouseListener(new MouseAdapter() {
@Override
public void mouseDown(MouseEvent e) {
for (final CFServiceWizUI service : allServicesList) {
if(service.getAppxLocation().contains(e.x, e.y)) {
selectItem(service);
break;
}
}
}
});
result.setTabList(tabList);
for(Control c : tabList) {
c.addTraverseListener(traverseListener);
}
return result;
}
@Override
public void setVisible(boolean isVis) {
super.setVisible(isVis);
if (isVis) {
// Set focus on scrolled composite whenever we become visible to
// prevent missing mouse wheel events
scrollComp.setFocus();
updateConfiguration();
updateServicesTable();
}
}
protected void updateServicesTable() {
Control content = scrollComp.getContent();
if (content != null) {
content.dispose();
}
if (allServicesList.isEmpty()) {
Composite labelComp = new Composite(scrollComp, SWT.NONE);
GridLayout layout = new GridLayout();
layout.horizontalSpacing = 8;
layout.verticalSpacing = 12;
labelComp.setLayout(layout);
labelComp.setBackground(scrollComp.getBackground());
Label descriptionLabel = new Label(labelComp, SWT.NONE);
descriptionLabel.setLayoutData(new GridData(GridData.FILL, GridData.BEGINNING, false, false));
descriptionLabel.setBackground(scrollComp.getBackground());
descriptionLabel.setText(Messages.CloudFoundryServiceWizardPage1_LABEL_NO_AVAIL_SERVICE);
scrollComp.setContent(labelComp);
labelComp.setSize(labelComp.computeSize(SWT.DEFAULT, SWT.DEFAULT));
return;
}
final Composite comp = new Composite(scrollComp, SWT.NONE);
comp.setBackground(scrollComp.getBackground());
comp.setLayout(new CFServiceWizardLayout(this));
comp.addPaintListener(new PaintListener() {
public void paintControl(PaintEvent event) {
List<CFServiceWizUI> selected = selectedServicesList;
if (selected != null && selected.size() == 1) {
Rectangle r = selected.get(0).getAppxLocation();
if (r != null) {
Rectangle r2 = new Rectangle(r.x, r.y, r.width, r.height);
r2.x -= 3;
r2.width += 6;
r2.y -= 3;
r2.height += 6;
event.gc.setBackground(selFillColor);
event.gc.fillRoundRectangle(r2.x, r2.y, r2.width, r2.height, 7, 7);
event.gc.setForeground(selEdgeColor);
event.gc.drawRoundRectangle(r2.x, r2.y, r2.width, r2.height, 7, 7);
}
}
}
});
for (final CFServiceWizUI item : allServicesList) {
Color c = null;
boolean areWeInSelectedList = selectedServicesList.contains(item);
if (areWeInSelectedList) {
c = selFillColor;
}
else {
c = comp.getBackground();
}
if (filter == null || filter.isEmpty() || matches(item, filter)) {
final Label imgLabel = new Label(comp, SWT.NONE);
imgLabel.setBackground(c);
imgLabel.setData(item);
if (loader != null) {
// // Uncomment out these lines for dynamic icon acquisition
// imgLabel.addPaintListener(new PaintListener() {
//
// public void paintControl(PaintEvent e) {
//
// if(imgLabel.getImage() == null) {
// loader.addIconToRetrieveList(item.getOffering(),
// imgLabel);
// }
// }
// });
loader.addIconToRetrieveList(item.getOffering(), imgLabel);
}
final Label nameLabel = new Label(comp, SWT.WRAP);
nameLabel.setFont(boldFont);
nameLabel.setBackground(c);
nameLabel.setText(item.getName());
nameLabel.setData(item);
final Label descLabel = new Label(comp, SWT.WRAP);
descLabel.setBackground(c);
String desc = item.getDescription();
if (desc == null) {
desc = ""; //$NON-NLS-1$
}
descLabel.setText(trimTextAtBound(desc, 200));
descLabel.setToolTipText(wrapAndTrimTextAtBound(desc, 100));
descLabel.setData(item);
String hyperLink = null;
if (hyperLink == null) {
hyperLink = item.getOffering().getUrl();
}
if (hyperLink == null) {
hyperLink = item.getOffering().getInfoUrl();
}
if (hyperLink == null) {
hyperLink = item.getOffering().getDocumentationUrl();
}
// final String hyperLinkFinal = hyperLink;
final Hyperlink hyperLinkLabel = new Hyperlink(comp, SWT.WRAP);
hyperLinkLabel.setBackground(c);
hyperLinkLabel.setText(""); //$NON-NLS-1$
hyperLinkLabel.setVisible(false);
// if(hyperLink != null) {
// hyperLinkLabel.setText("(link)");
// hyperLinkLabel.setForeground(comp.getDisplay().getSystemColor(SWT.COLOR_DARK_BLUE));
// hyperLinkLabel.setUnderlined(true);
// hyperLinkLabel.addHyperlinkListener(new HyperlinkAdapter() {
// public void linkActivated(HyperlinkEvent e) {
// if(hyperLinkFinal != null) {
// org.eclipse.swt.program.Program.launch(hyperLinkFinal);
// }
// }
// });
// }
CFWizSelectItemListener itemListener = new CFWizSelectItemListener(item);
imgLabel.addMouseListener(itemListener);
nameLabel.addMouseListener(itemListener);
descLabel.addMouseListener(itemListener);
if (item != null){
if (item.getImageLabel() != null)
item.getImageLabel().setVisible(true);
if (item.getNameLabel() != null)
item.getNameLabel().setVisible(true);
if (item.getDescriptionLabel() != null)
item.getDescriptionLabel().setVisible(true);
}
}
else {
// Filtered out items have no location
item.setAppxLocation(null);
if (item != null){
if (item.getImageLabel() != null)
item.getImageLabel().setVisible(false);
if (item.getNameLabel() != null)
item.getNameLabel().setVisible(false);
if (item.getDescriptionLabel() != null)
item.getDescriptionLabel().setVisible(false);
}
}
}
if (layoutList != null)
layoutList.layout();
comp.addMouseListener(new MouseListener() {
public void mouseDown(MouseEvent e) {
for (final CFServiceWizUI item : allServicesList) {
// Skip filtered items that has null appxLocation.
if (item.getAppxLocation() != null && item.getAppxLocation().contains(e.x, e.y)) {
selectItem(item);
break;
}
}
}
public void mouseDoubleClick(MouseEvent arg0) {
// Ignore
}
public void mouseUp(MouseEvent arg0) {
// Ignore
}
});
scrollComp.setContent(comp);
Rectangle r = scrollComp.getClientArea();
r.height = comp.computeSize(r.width, SWT.DEFAULT).y;
comp.setBounds(r);
for (Control c : serviceInfoComposite.getChildren()) {
c.dispose();
}
createServiceInfoComposite();
serviceInfoComposite.layout();
topComp.layout();
servicePageValidation.updatePageState();
}
private void createServiceInfoComposite() {
String name = ""; //$NON-NLS-1$
final CFServiceWizUI selectedItemFinal;
if (selectedServicesList.size() == 1) {
selectedItemFinal = selectedServicesList.get(0);
name = selectedItemFinal.getName();
}
else {
selectedItemFinal = null;
}
serviceInfoComposite.setEnabled(selectedItemFinal != null);
final Label serviceNameLabel = new Label(serviceInfoComposite, SWT.NONE);
serviceNameLabel.setText(Messages.COMMONTXT_NAME_WITH_COLON);
GridData data = new GridData(GridData.BEGINNING, GridData.CENTER, false, false);
serviceNameLabel.setLayoutData(data);
serviceNameLabel.setEnabled(selectedItemFinal != null);
serviceNameText = new Text(serviceInfoComposite, SWT.SEARCH);
serviceNameText.setLayoutData(new GridData(GridData.FILL, GridData.BEGINNING, true, false, 1, 1));
serviceNameText.setText(utilRemoveSpaces(name));
serviceNameText.setEnabled(selectedItemFinal != null);
serviceNameText.addTraverseListener(traverseListener);
if (selectedItemFinal != null) {
selectedItemFinal.setUserDefinedName(utilRemoveSpaces(name));
}
serviceNameText.addModifyListener(new ModifyListener() {
public void modifyText(ModifyEvent e) {
String text = serviceNameText.getText();
if (selectedItemFinal != null) {
selectedItemFinal.setUserDefinedName(text);
}
servicePageValidation.updatePageState();
}
});
final Label servicePlanLabel = new Label(serviceInfoComposite, SWT.NONE);
servicePlanLabel.setText(Messages.CloudFoundryServiceWizardPage1_LABEL_PLAN);
servicePlanLabel.setEnabled(selectedItemFinal != null);
data = new GridData(GridData.BEGINNING, GridData.CENTER, false, false);
servicePlanLabel.setLayoutData(data);
@SuppressWarnings("unchecked")
final List<CloudServicePlan> plans = selectedItemFinal != null ? selectedItemFinal.getOffering()
.getCloudServicePlans() : Collections.EMPTY_LIST;
String[] items = new String[plans.size()];
for (int x = 0; x < items.length; x++) {
items[x] = plans.get(x).getName();
}
final Combo typeCombo = new Combo(serviceInfoComposite, SWT.READ_ONLY | SWT.BORDER);
typeCombo.setLayoutData(new GridData(GridData.FILL, GridData.BEGINNING, true, false, 1, 1));
typeCombo.setItems(items);
typeCombo.select(0);
typeCombo.setEnabled(selectedItemFinal != null);
typeCombo.addSelectionListener(new SelectionListener() {
public void widgetSelected(SelectionEvent e) {
if (selectedItemFinal != null) {
selectedItemFinal.setPlan(plans.get(typeCombo.getSelectionIndex()));
}
}
public void widgetDefaultSelected(SelectionEvent e) {
// Ignore.
}
});
serviceInfoComposite.setVisible(true);
}
private static String utilRemoveSpaces(String str) {
if (str == null) {
return null;
}
else {
return str.replace(" ", ""); //$NON-NLS-1$ //$NON-NLS-2$
}
}
private void selectItem(CFServiceWizUI item) {
if (selectedServicesList == null)
return;
if (selectedServicesList.contains(item)) {
// Do nothing to disable deselect.
return;
}
else {
selectedServicesList.clear();
selectedServicesList.add(item);
// If the plan has not been set, use the first
if (item != null && item.getPlan() == null) {
List<CloudServicePlan> plans = item.getOffering().getCloudServicePlans();
if (plans != null && plans.size() > 0) {
item.setPlan(plans.get(0));
}
}
}
servicePageValidation.updatePageState();
Point p = scrollComp.getOrigin();
updateServicesTable();
scrollComp.setFocus();
scrollComp.setOrigin(p);
if (selectedServicesList.size() > 0){
selectServiceListItem(selectedServicesList.get(0));
}
}
/** Update the selection rectangle in the composite for the service*/
private void selectServiceListItem(CFServiceWizUI service) {
if (service == null)
return;
if(selectedServicesList != null && selectedServicesList.size() > 0) {
// Set the previously selected item back to normal
selectedServicesList.get(0).getDescriptionLabel().setBackground(layoutList.getBackground());
selectedServicesList.get(0).getImageLabel().setBackground(layoutList.getBackground());
selectedServicesList.get(0).getNameLabel().setBackground(layoutList.getBackground());
}
service.getDescriptionLabel().setBackground(selFillColor);
service.getImageLabel().setBackground(selFillColor);
service.getNameLabel().setBackground(selFillColor);
service.getNameLabel().forceFocus();
layoutList.redraw();
if(service.getAppxLocation() != null) {
Rectangle bounds = service.getAppxLocation(); //child.getBounds();
Rectangle area = scrollComp.getClientArea();
Point origin = scrollComp.getOrigin();
// Our view is lower than the item
if (origin.y > bounds.y) {
origin.y = Math.max(0, bounds.y);
}
// Our view is above the item
if (origin.y + area.height < bounds.y + bounds.height) {
origin.y = Math.max(0, bounds.y + bounds.height - area.height);
}
scrollComp.setOrigin(origin);
}
}
class CFWizSelectItemListener implements SelectionListener, MouseListener {
CFServiceWizUI service;
public CFWizSelectItemListener(CFServiceWizUI item2) {
this.service = item2;
}
public void widgetSelected(SelectionEvent event2) {
selectItem(service);
}
public void mouseDoubleClick(MouseEvent arg0) {
// Ignore.
}
public void mouseDown(MouseEvent arg0) {
selectItem(service);
}
public void mouseUp(MouseEvent arg0) {
// Ignore.
}
public void widgetDefaultSelected(SelectionEvent arg0) {
// Ignore.
}
}
protected boolean matches(CFServiceWizUI item, String filter) {
return item.getName().toLowerCase().contains(filter.toLowerCase())
|| item.getDescription().toLowerCase().contains(filter.toLowerCase());
}
public String getServiceName() {
return nameText.getText();
}
public List<CFServiceWizUI> getSelectedList() {
return selectedServicesList;
}
protected void initializeDialogUnits(Control testControl) {
// Compute and store a font metric
GC gc = new GC(testControl);
gc.setFont(JFaceResources.getDialogFont());
fontMetrics = gc.getFontMetrics();
gc.dispose();
}
public int convertHorizontalDLUsToPixels(Composite comp, int x) {
if (fontMetrics == null) {
initializeDialogUnits(comp);
}
return Dialog.convertHorizontalDLUsToPixels(fontMetrics, x);
}
public int convertVerticalDLUsToPixels(Composite comp, int y) {
if (fontMetrics == null) {
initializeDialogUnits(comp);
}
return Dialog.convertVerticalDLUsToPixels(fontMetrics, y);
}
private static String trimTextAtBound(String str, int bound) {
if (str.length() > bound) {
/** Use the existing word wrap function to wrap at the spot we want. */
String result = wrapAndTrimTextAtBound(str, bound);
int index = result.indexOf("\n"); //$NON-NLS-1$
if (index == -1) {
return str;
}
else {
return str.substring(0, index) + "..."; //$NON-NLS-1$
}
}
else {
return str;
}
}
/** Simple text wrapper */
private static String wrapAndTrimTextAtBound(String text, int bound) {
StringBuilder result = new StringBuilder();
String remainingString = text;
// Last seen in the text
int lastSpace = 0;
for (int x = 0; x < remainingString.length(); x++) {
if (remainingString.charAt(x) == ' ') {
lastSpace = x;
}
if (x == bound) {
// If there is no way to possible wrap it (e.g no spaces, or the
// bound is too small) then don't bother
if (lastSpace == 0) {
return text;
}
String nextLine = remainingString.substring(0, lastSpace);
remainingString = remainingString.substring(lastSpace).trim();
result.append(nextLine);
result.append("\n"); //$NON-NLS-1$
x = 0;
lastSpace = 0;
}
}
result.append(remainingString.trim());
return result.toString();
}
protected boolean isIconProviderAvailable() {
return loader != null;
}
@Override
public void dispose() {
super.dispose();
if (loader != null) {
this.loader.dispose();
}
}
class ServiceListTraverseListener implements TraverseListener {
@Override
public void keyTraversed(TraverseEvent e) {
if( ((e.getSource() == filterText ) && (e.detail == SWT.TRAVERSE_TAB_NEXT))
|| ((e.getSource() == serviceNameText) && (e.detail == SWT.TRAVERSE_TAB_PREVIOUS))){
// Only one service can currently be selected right now
int listSize = selectedServicesList.size();
if (selectedServicesList != null && allServicesList != null){
// An item was previously selected. Select that item again
if (listSize > 0){
CFServiceWizUI item = selectedServicesList.get(0);
if (item != null && item.getNameLabel() != null && item.getNameLabel().isVisible()){
selectServiceListItem(item);
// If doit is set to true, then the cursor will change focus to
// the next text box. That behaviour is not good as it is difficult
// for accessibility to lose focus of the items in the list
e.doit = false;
return;
}
}
// Item is no longer selected (e.g. filter is used), so choose another item
CFServiceWizUI service = null;
int len = allServicesList.size();
for (int i=0;i<len;i++){
CFServiceWizUI currItem = allServicesList.get(i);
if (currItem != null && currItem.getNameLabel() != null){
if(allServicesList.get(i).getNameLabel().isVisible()){
service = allServicesList.get(i);
break;
}
}
}
// Service can be null if filter leads to 0 results
if (service != null){
selectItem(service);
}
else {
// If there are no items, the user is stuck in the keyboard only case if
// they tab next. Focus on the next text box instead
if (((e.getSource() == filterText ) && (e.detail == SWT.TRAVERSE_TAB_NEXT)) && serviceNameText != null){
serviceNameText.forceFocus();
}
else if (((e.getSource() == serviceNameText) && (e.detail == SWT.TRAVERSE_TAB_PREVIOUS)) && filterText != null){
filterText.forceFocus();
}
}
}
// If doit is set to true, then the cursor will change focus to
// the next text box. That behaviour is not good as it is difficult
// for accessibility to lose focus of the items in the list
e.doit = false;
}
else {
if(e.getSource() instanceof Label && e.getSource() != null &&
((Label)e.getSource() ).getData() instanceof CFServiceWizUI
&& allServicesList != null) {
// If you press tab while in the list, go outside of the list
if(e.detail == SWT.TRAVERSE_TAB_NEXT) {
if (serviceNameText != null)
serviceNameText.forceFocus();
}
if (e.detail == SWT.TRAVERSE_TAB_PREVIOUS){
if (filterText != null)
filterText.forceFocus();
}
CFServiceWizUI selectedService = (CFServiceWizUI)((Label)e.getSource() ).getData();
CFServiceWizUI service = null;
int servicePos = -1;
for(int x = 0; x < allServicesList.size(); x++) {
if(selectedService == allServicesList.get(x)) {
servicePos = x;
break;
}
}
// If you press the arrow keys inside the list, go to the next visible item
if(servicePos != -1) {
if(e.detail == SWT.TRAVERSE_ARROW_NEXT) {
int len = allServicesList.size();
while(servicePos+1 < len){
servicePos++;
CFServiceWizUI currItem = allServicesList.get(servicePos);
if (currItem != null && currItem.getNameLabel() != null && currItem.getNameLabel().isVisible()){
service = allServicesList.get(servicePos);
break;
}
}
} else if(e.detail == SWT.TRAVERSE_ARROW_PREVIOUS) {
while(servicePos > 0){
servicePos--;
CFServiceWizUI currItem = allServicesList.get(servicePos);
if (currItem != null && currItem.getNameLabel() != null && currItem.getNameLabel().isVisible()){
service = allServicesList.get(servicePos);
break;
}
}
}
}
if(service != null) {
selectItem(service);
}
}
}
}
}
}
class CFServiceWizUI {
private CloudServiceOffering offering;
private Button button;
private Rectangle appxLocation;
private String userDefinedName = ""; //$NON-NLS-1$
private CloudServicePlan plan = null;
Label nameLabel;
Label descLabel;
Label imageLabel;
public CFServiceWizUI(CloudServiceOffering offering) {
this.offering = offering;
}
public String getName() {
return offering.getName();
}
public String getDescription() {
return offering.getDescription();
}
public Button getButton() {
return button;
}
public void setButton(Button button) {
this.button = button;
}
public Rectangle getAppxLocation() {
return appxLocation;
}
public void setAppxLocation(Rectangle appxLocation) {
this.appxLocation = appxLocation;
}
public String getUserDefinedName() {
return userDefinedName;
}
public void setUserDefinedName(String userDefinedName) {
this.userDefinedName = userDefinedName;
}
public CloudServiceOffering getOffering() {
return offering;
}
public CloudServicePlan getPlan() {
return plan;
}
public void setPlan(CloudServicePlan plan) {
this.plan = plan;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof CFServiceWizUI)) {
return false;
}
CloudServiceOffering offering = ((CFServiceWizUI) o).getOffering();
String name1 = "" + getOffering().getName(); //$NON-NLS-1$
String name2 = "" + offering.getName(); //$NON-NLS-1$
return name1.equals(name2);
}
public LocalCloudService convertToLocalCloudService() {
final LocalCloudService localService = new LocalCloudService(""); //$NON-NLS-1$
localService.setName(getUserDefinedName());
localService.setVersion(getOffering().getVersion());
localService.setLabel(getOffering().getLabel());
localService.setProvider(getOffering().getProvider());
localService.setPlan(getPlan().getName());
return localService;
}
public Label getNameLabel() {
return nameLabel;
}
public void setNameLabel(Label nameLabel) {
this.nameLabel = nameLabel;
}
public Label getDescriptionLabel() {
return descLabel;
}
public void setDescriptionLabel(Label descLabel) {
this.descLabel = descLabel;
}
public Label getImageLabel() {
return imageLabel;
}
public void setImageLabel(Label imageLabel) {
this.imageLabel = imageLabel;
}
}
class CFServiceWizardLayout extends Layout {
private static final int BORDER = 5;
private static int IMG_DEFAULT = 32;
private static final int GAP = 3;
private static final int V_SPACE = 3;
protected int colWidth = -1;
CloudFoundryServiceWizardPage1 parent;
public CFServiceWizardLayout(CloudFoundryServiceWizardPage1 parent) {
this.parent = parent;
}
@Override
protected Point computeSize(Composite composite, int wHint, int hHint, boolean flushCache) {
int[] result = layoutImpl(composite, 0, flushCache, false);
return new Point(result[0], result[1]);
}
@Override
protected void layout(Composite composite, boolean flushCache) {
int[] result = layoutImpl(composite, 0, flushCache, true);
// Adjust the height to match the elments inside
Rectangle r = composite.getBounds();
r.height = result[1];
composite.setBounds(r);
}
protected int[] layoutImpl(Composite composite, int wHint, boolean flushCache, boolean apply) {
Rectangle r = composite.getClientArea();
int areaW = r.width > 0 ? r.width : wHint;
int i = 0;
int x = r.x + BORDER;
int y = r.y + BORDER;
int w = 0;
if (areaW == 0) {
w = composite.getParent().getBounds().width - composite.getParent().getBorderWidth() * 2;
}
else {
w = areaW - BORDER * 2;
}
w = Math.max(400, w);
int bottomMostY = -1;
int rightMostX = -1;
boolean iconsAvailable = parent.isIconProviderAvailable();
int IMG;
if (iconsAvailable) {
IMG = IMG_DEFAULT;
}
else {
IMG = 0;
}
// children are: image, name, description, size
Control[] children = composite.getChildren();
final int initialX = x;
while (i < children.length) {
x = initialX;
if (i > 0) {
y += V_SPACE;
}
Label localIconLabel = (Label) children[i];
Label localNameLabel = (Label) children[i + 1];
CFServiceWizUI product = (CFServiceWizUI) localNameLabel.getData();
// image
if (apply) {
localIconLabel.setBounds(x, y, IMG, IMG);
}
if (iconsAvailable) {
x += 3;
}
// label
Point p = localNameLabel.computeSize(w - IMG - GAP, SWT.DEFAULT, flushCache);
if (apply) {
localNameLabel.setBounds(x + IMG + GAP, y, p.x, p.y);
}
y += p.y + GAP;
// hyperlink
Hyperlink localLink = (Hyperlink) children[i + 3];
p = localLink.computeSize(SWT.DEFAULT, SWT.DEFAULT, flushCache);
if (apply) {
localLink.setBounds(x + w - p.x - 2, y, p.x, p.y);
}
// description
Label localDescr = (Label) children[i + 2];
p = localDescr.computeSize(w - IMG - GAP * 2, SWT.DEFAULT, flushCache);
if (apply) {
localDescr.setBounds(x + IMG + GAP, y, p.x, p.y);
}
y = y + p.y;
Rectangle imgLabelRect = localIconLabel.getBounds();
int topLeftX = imgLabelRect.x;
int topLeftY = imgLabelRect.y;
Rectangle descLabelRect = localDescr.getBounds();
int botRightX = descLabelRect.x + descLabelRect.width;
int botRightY = descLabelRect.y + descLabelRect.height;
bottomMostY = botRightY;
rightMostX = botRightX;
Rectangle rowPos = new Rectangle(topLeftX, topLeftY, botRightX - topLeftX, botRightY - topLeftY);
if (apply) {
product.setAppxLocation(rowPos);
}
y += BORDER;
i += 4;
}
bottomMostY += BORDER;
return new int[] { rightMostX, bottomMostY };
}
}
class CFWizServicePage1Validation {
private static Pattern VALID_CHARS = Pattern.compile("[A-Za-z\\$_0-9\\-]+"); //$NON-NLS-1$
CloudFoundryServiceWizardPage1 wizardPage;
public CFWizServicePage1Validation(CloudFoundryServiceWizardPage1 wizardPage) {
this.wizardPage = wizardPage;
}
public void updatePageState() {
boolean descriptionUpdated = false;
if (!descriptionUpdated) {
List<CFServiceWizUI> list = wizardPage.getSelectedList();
if (list == null || list.size() == 0) {
wizardPage.setDescription(Messages.CloudFoundryServiceWizardPage1_TEXT_SELECT_SERVICE_LIST);
wizardPage.setErrorMessage(null);
descriptionUpdated = true;
}
}
if (!descriptionUpdated) {
List<CFServiceWizUI> list = wizardPage.getSelectedList();
if (list != null && list.size() == 1) {
CFServiceWizUI service = list.get(0);
String userDefinedName = service.getUserDefinedName();
if (service != null && userDefinedName != null) {
if (userDefinedName.trim().length() == 0) {
wizardPage.setDescription(Messages.CloudFoundryServiceWizardPage1_TEXT_SET_SERVICE_NAME);
wizardPage.setErrorMessage(null);
descriptionUpdated = true;
}
Matcher matcher = VALID_CHARS.matcher(userDefinedName);
if (!descriptionUpdated && !matcher.matches()) {
wizardPage.setErrorMessage(Messages.CloudFoundryServiceWizardPage1_ERROR_INVALID_CHAR);
wizardPage.setDescription(null);
descriptionUpdated = true;
}
}
}
}
if (!descriptionUpdated) {
wizardPage.setErrorMessage(null);
wizardPage.setDescription(CloudFoundryServiceWizardPage1.DESCRIPTION_FINAL);
wizardPage.setPageComplete(true);
}
else {
wizardPage.setPageComplete(false);
}
}
}
class CFServiceWizardDynamicIconLoader extends Thread {
private final Object lock = new Object();
// List synchronized on lock
private List<ServiceWizardMapEntry> iconsToRetrieve = new ArrayList<ServiceWizardMapEntry>();
private CloudFoundryServer server;
private ICloudFoundryServiceWizardIconProvider iconProvider;
private boolean isRunning = true;
// Synchronize on imageMap when accessing it
private Map<String /* service id + provider */, Image> imageMap = new HashMap<String /*
* offering
* id
*/, Image>();
public CFServiceWizardDynamicIconLoader(ICloudFoundryServiceWizardIconProvider iconProvider,
CloudFoundryServer server) {
super(CFServiceWizardDynamicIconLoader.class.getName());
setDaemon(true);
this.iconProvider = iconProvider;
this.server = server;
}
/**
* Add icon to the front of the list, for icons the user is currently
* viewing
*/
public void addIconToFrontOfRetrieveList(CloudServiceOffering offering, Label imageLabel) {
synchronized (lock) {
// Add to front (this may create a duplicate; dupes are checked in
// IconRetrieveRunnable)
ServiceWizardMapEntry me = new ServiceWizardMapEntry(offering, imageLabel);
iconsToRetrieve.add(0, me);
lock.notifyAll();
}
}
/** Add icon to end of list */
public void addIconToRetrieveList(CloudServiceOffering offering, Label imageLabel) {
ServiceWizardMapEntry me = new ServiceWizardMapEntry(offering, imageLabel);
synchronized (lock) {
iconsToRetrieve.add(me);
lock.notifyAll();
}
}
public void run() {
// Have up to 5 URL requests running at a time
ExecutorService es = Executors.newFixedThreadPool(5);
while (isRunning) {
try {
List<ServiceWizardMapEntry> localIconsToRetrieve = null;
synchronized (lock) {
lock.wait(1000);
if (isRunning && iconsToRetrieve.size() > 0) {
localIconsToRetrieve = new ArrayList<ServiceWizardMapEntry>();
localIconsToRetrieve.addAll(iconsToRetrieve);
iconsToRetrieve.clear();
}
}
if (isRunning && localIconsToRetrieve != null) {
// Process icon requests outside the lock
for (ServiceWizardMapEntry e : localIconsToRetrieve) {
IconRetrieveRunnable r = new IconRetrieveRunnable(e);
es.execute(r);
}
localIconsToRetrieve.clear();
}
}
catch (InterruptedException e) {
isRunning = false;
}
}
es.shutdownNow();
try {
// Wait at most 10 seconds for the remaining tasks to finish
es.awaitTermination(10, TimeUnit.SECONDS);
}
catch (InterruptedException e1) {
// ignore.
}
synchronized (imageMap) {
// Dispose of old images
Set<Entry<String, Image>> s = imageMap.entrySet();
for (Entry<String, Image> e : s) {
if (e.getValue().isDisposed()) {
e.getValue().dispose();
}
}
}
}
public void dispose() {
synchronized (lock) {
isRunning = false;
}
}
private class IconRetrieveRunnable implements Runnable {
ServiceWizardMapEntry entry;
public IconRetrieveRunnable(ServiceWizardMapEntry entry) {
this.entry = entry;
}
public void run() {
CloudServiceOffering cso = entry.getOffering();
final String mapId = "" + cso.getName() + "-" + cso.getProvider(); //$NON-NLS-1$ //$NON-NLS-2$
Image result = null;
// Check the cache for the image
synchronized (imageMap) {
Image i = imageMap.get(mapId);
if (i != null) {
result = i;
}
}
// Grab the image from the provider, if needed
if (result == null) {
ImageDescriptor imageDesc = iconProvider.getServiceIcon(entry.getOffering(), server);
if (imageDesc != null) {
Image img = imageDesc.createImage();
final Image resizeImg = resizeImage(img, 32, 32);
result = resizeImg;
synchronized (imageMap) {
imageMap.put(mapId, resizeImg);
}
}
}
final Image labelImage = result;
if (labelImage == null)
return;
// Replace the image label
Display.getDefault().asyncExec(new Runnable() {
public void run() {
Label l = entry.getImageLabel();
if (!l.isDisposed()) {
l.setImage(labelImage);
}
}
});
}
private Image resizeImage(Image oldImage, int newWidth, int newHeight) {
Image newImage = new Image(Display.getDefault(), newWidth, newHeight);
GC gc = new GC(newImage);
gc.setAntialias(SWT.ON);
gc.setInterpolation(SWT.HIGH);
gc.drawImage(oldImage, 0, 0, oldImage.getBounds().width, oldImage.getBounds().height, 0, 0, newWidth,
newHeight);
gc.dispose();
oldImage.dispose();
return newImage;
}
}
private static class ServiceWizardMapEntry {
private CloudServiceOffering offering;
private Label imageLabel;
public ServiceWizardMapEntry(CloudServiceOffering offering, Label imageLabel) {
super();
this.offering = offering;
this.imageLabel = imageLabel;
}
public CloudServiceOffering getOffering() {
return offering;
}
public Label getImageLabel() {
return imageLabel;
}
}
}