/*
* Copyright 2011-2012 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.core.ui;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.layout.TableColumnLayout;
import org.eclipse.jface.viewers.ColumnWeightData;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.TableEditor;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.swt.widgets.Text;
import com.amazonaws.eclipse.core.AwsToolkitCore;
/**
* Simple table dialog to allow use user to enter multiple values for an
* attribute.
*/
public class MultiValueEditorDialog extends MessageDialog {
private static final String NEW_VALUE = "<new value>";
private boolean editLocked = false;
private int lockedRowIndex = -1;
protected final List<String> values = new ArrayList<String>();
protected String columnText = "Attributes";
public List<String> getValues() {
return this.values;
}
protected TableViewer tableViewer;
/**
* Default constructor provides "OK" and "Cancel" buttons with an AWS logo.
*/
public MultiValueEditorDialog(final Shell parentShell) {
super(parentShell, "Edit values", AwsToolkitCore.getDefault().getImageRegistry()
.get(AwsToolkitCore.IMAGE_AWS_ICON), "", MessageDialog.NONE, new String[] { "OK", "Cancel" }, 0);
}
/**
* Full featured constructor from {@link MessageDialog}
*/
public MultiValueEditorDialog(Shell parentShell, String dialogTitle, Image dialogTitleImage, String dialogMessage,
int dialogImageType, String[] dialogButtonLabels, int defaultIndex) {
super(parentShell, dialogTitle, dialogTitleImage, dialogMessage, dialogImageType, dialogButtonLabels, defaultIndex);
}
@Override
protected boolean isResizable() {
return true;
}
@Override
protected Control createDialogArea(final Composite parent) {
Composite composite = new Composite(parent, SWT.None);
GridDataFactory.fillDefaults().grab(true, true).span(2, 1).applyTo(composite);
TableColumnLayout layout = new TableColumnLayout();
composite.setLayout(layout);
this.tableViewer = new TableViewer(composite);
this.tableViewer.getTable().setHeaderVisible(true);
TableColumn tableColumn = new TableColumn(this.tableViewer.getTable(), SWT.NONE);
tableColumn.setText(columnText);
layout.setColumnData(tableColumn, new ColumnWeightData(100));
this.tableViewer.setContentProvider(new AbstractTableContentProvider() {
@Override
public Object[] getElements(final Object inputElement) {
Object[] rowsPlusNew = new Object[MultiValueEditorDialog.this.values.size() + 1];
MultiValueEditorDialog.this.values.toArray(rowsPlusNew);
rowsPlusNew[rowsPlusNew.length - 1] = NEW_VALUE;
return rowsPlusNew;
}
});
this.tableViewer.setLabelProvider(new AbstractTableLabelProvider() {
@Override
public String getColumnText(final Object element, final int columnIndex) {
return (String) element;
}
});
final Table table = this.tableViewer.getTable();
final TableEditor editor = new TableEditor(table);
editor.horizontalAlignment = SWT.LEFT;
editor.grabHorizontal = true;
table.addListener(SWT.MouseUp, new Listener() {
public void handleEvent(final Event event) {
Rectangle clientArea = table.getClientArea();
Point pt = new Point(event.x, event.y);
int index = table.getTopIndex();
while ( index < table.getItemCount() ) {
boolean visible = false;
final TableItem item = table.getItem(index);
// Only one column, but loop is here for completeness
for ( int i = 0; i < table.getColumnCount(); i++ ) {
Rectangle rect = item.getBounds(i);
if ( rect.contains(pt) ) {
final int column = i;
final Text text = new Text(table, SWT.NONE);
final int idx = index;
if ( isRowUneditable(idx) ) {
return;
}
Listener textListener = new Listener() {
public void handleEvent(final Event e) {
if ( e.type == SWT.Traverse && e.detail == SWT.TRAVERSE_ESCAPE ) {
/* Skip data validation and dispose the text editor */
text.dispose();
e.doit = false;
return;
} else if ( e.type == SWT.Traverse && e.detail != SWT.TRAVERSE_RETURN ) {
/* No-op for keys other than escape or return. */
return;
} else {
/* For all other events, we first validate the data */
if ( !validateAttributeValue(text.getText()) ) {
lockTableEditor(idx);
return;
}
/* First unlock everything */
unlockTableEditor();
/* Then we handle different events */
if ( e.type == SWT.FocusOut ) {
modifyValue(item, column, idx, text);
text.dispose();
} else if ( e.type == SWT.Traverse && e.detail == SWT.TRAVERSE_RETURN ) {
modifyValue(item, column, idx, text);
} else if ( e.type == SWT.Modify ) {
/* No-op */
}
}
}
};
text.addListener(SWT.FocusOut, textListener);
text.addListener(SWT.Traverse, textListener);
text.addListener(SWT.Modify, textListener);
editor.setEditor(text, item, i);
text.setText(item.getText(i));
text.selectAll();
text.setFocus();
return;
}
if ( !visible && rect.intersects(clientArea) ) {
visible = true;
}
}
if ( !visible ) {
return;
}
index++;
}
}
});
/* Suppress changing to other table rows when the editor is locked. */
this.tableViewer.addSelectionChangedListener(new ISelectionChangedListener() {
private boolean update = true;
private ISelection lastSelection;
public void selectionChanged(SelectionChangedEvent event) {
if ( update && isLocked() ) {
update = false; // avoid infinite loop
tableViewer.setSelection(lastSelection);
update = true;
} else if ( !isLocked() ) {
lastSelection = event.getSelection();
}
}
});
this.tableViewer.setInput(values.size());
this.tableViewer.getTable().getItem(this.values.size())
.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_DARK_GRAY));
return composite;
}
/**
* Called when a value in the list is modified.
*/
protected void modifyValue(final TableItem item, final int column, final int index, final Text text) {
String newValue = text.getText();
if ( newValue.length() == 0 ) {
if ( index < this.values.size() ) {
this.values.remove(index);
}
this.tableViewer.refresh();
} else {
item.setText(column, newValue);
if ( index == item.getParent().getItemCount() - 1 ) {
this.values.add(newValue);
this.tableViewer.refresh();
item.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_BLACK));
this.tableViewer.getTable().getItem(this.values.size())
.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_DARK_GRAY));
} else {
this.values.set(index, newValue);
}
}
}
/**
* Base class always returns true when validating the data.
*/
protected boolean validateAttributeValue(String attributeValue) {
return true;
}
/**
* Add a customized suffix for the column text.
*/
protected void addColumnTextDescription(String columnTextSuffix) {
this.columnText = this.columnText + " " + columnTextSuffix;
}
/**
* @param index
* The index of the locked row.
*/
protected void lockTableEditor(int index) {
editLocked = true;
lockedRowIndex = index;
this.getButton(0).setEnabled(false);
this.getButtonBar().update();
}
protected void unlockTableEditor() {
editLocked = false;
lockedRowIndex = -1;
this.getButton(0).setEnabled(true);
this.getButtonBar().update();
}
private boolean isLocked() {
return editLocked;
}
private boolean isRowUneditable(int index) {
return editLocked && index != lockedRowIndex;
}
}