/*
* $Id$
*
* Copyright (c) 2004-2005 by the TeXlapse Team.
* 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 net.sourceforge.texlipse.properties;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import net.sourceforge.texlipse.PathUtils;
import net.sourceforge.texlipse.TexlipsePlugin;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.IInputValidator;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.preference.FieldEditor;
import org.eclipse.jface.viewers.ColumnWeightData;
import org.eclipse.jface.viewers.DoubleClickEvent;
import org.eclipse.jface.viewers.IDoubleClickListener;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITableLabelProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.TableLayout;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerSorter;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.List;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.Text;
/**
* A FieldEditor that holds a list of key-value-pairs as its value.
*
* The list of strings is converted into a single String object by
* putting a separator character between the items while concatenating.
*
* @author Kimmo Karlsson
*/
public class KeyValueListFieldEditor extends FieldEditor implements IInputValidator {
// separator character for strings
public static final String SEPARATOR = ",";
// list of invalid characters that can't be in a keyword
private static final String INVALID_CHARS = " ,=*()[]{}<>|\\?+/%$¤£#@\"!§½";
private static final String ENV_TABLE_KEY = "key";
private static final String ENV_TABLE_VALUE = "value";
// component holding environment variable table and label
private Composite table;
// environment variable table component
private TableViewer environmentTable;
// for adding new variables
private Button envAddButton;
// for removing variables
private Button envRemoveButton;
// for editing existing variables
private Button envEditButton;
// for importing variables from the shell environment
private Button envImportButton;
// the environment variables
private Map environment;
/**
* Environment variable for the environment table.
* @see org.eclipse.debug.internal.ui.launchConfigurations.EnvironmentVariable
*/
class EnvironmentVariable {
// The name of the environment variable
private String name;
// The value of the environment variable
private String value;
/**
* Create new key/value -pair.
* @param name key
* @param value value
*/
public EnvironmentVariable(String name, String value) {
this.name = name;
this.value = value;
}
/**
* Returns this variable's name, which serves as the key in the key/value
* pair this variable represents
*
* @return this variable's name
*/
public String getName() {
return name;
}
/**
* Returns this variables value.
* @return this variable's value
*/
public String getValue() {
return value;
}
/**
* Sets this variable's name (key)
* @param name name
*/
public void setName(String name) {
this.name = name;
}
/**
* Sets this variable's value
* @param value value
*/
public void setValue(String value) {
this.value = value;
}
/**
* Returns this variable's name.
* @return this variable's name
*/
public String toString() {
return getName();
}
/**
* Compare names.
*/
public boolean equals(Object obj) {
boolean equal = false;
if (obj instanceof EnvironmentVariable) {
EnvironmentVariable var = (EnvironmentVariable)obj;
equal = var.getName().equals(name);
}
return equal;
}
/**
* @return name.hashCode()
*/
public int hashCode() {
return name.hashCode();
}
}
/**
* Content provider for the environment table.
* @see org.eclipse.debug.ui.EnvironmentTab.EnvironmentVariableContentProvider
*/
class EnvironmentVariableContentProvider implements IStructuredContentProvider {
public Object[] getElements(Object inputElement) {
EnvironmentVariable[] elements = new EnvironmentVariable[0];
Map m = (Map) inputElement;
if (m != null && !m.isEmpty()) {
elements = new EnvironmentVariable[m.size()];
String[] varNames = new String[m.size()];
m.keySet().toArray(varNames);
for (int i = 0; i < m.size(); i++) {
elements[i] = new EnvironmentVariable(varNames[i], (String) m.get(varNames[i]));
}
}
return elements;
}
public void dispose() {
}
public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
if (newInput == null){
return;
}
if (viewer instanceof TableViewer){
TableViewer tableViewer = (TableViewer) viewer;
if (tableViewer.getTable().isDisposed()) {
return;
}
tableViewer.setSorter(new ViewerSorter() {
public int compare(Viewer iviewer, Object e1, Object e2) {
if (e1 == null) {
return -1;
} else if (e2 == null) {
return 1;
} else {
return ((EnvironmentVariable)e1).getName().compareToIgnoreCase(((EnvironmentVariable)e2).getName());
}
}
});
}
}
}
/**
* Label provider for the environment table.
* @see org.eclipse.debug.ui.EnvironmentTab.EnvironmentVariableLabelProvider
*/
class EnvironmentVariableLabelProvider extends LabelProvider implements ITableLabelProvider {
public String getColumnText(Object element, int columnIndex) {
String result = null;
if (element != null) {
EnvironmentVariable var = (EnvironmentVariable) element;
switch (columnIndex) {
case 0: // variable
result = var.getName();
break;
case 1: // value
result = var.getValue();
break;
}
}
return result;
}
public Image getColumnImage(Object element, int columnIndex) {
return null;
}
}
/**
* Dialog to input variable name and value.
*/
class KeyValueInputDialog extends MessageDialog {
private IInputValidator validator;
private Text keyTextField;
protected String key;
private Text valueTextField;
protected String value;
/**
* Default constructor from super class. Do not use.
*
* @param parentShell
* @param dialogTitle
* @param dialogTitleImage
* @param dialogMessage
* @param dialogImageType
* @param dialogButtonLabels
* @param defaultIndex
*/
public KeyValueInputDialog(Shell parentShell, String dialogTitle, Image dialogTitleImage, String dialogMessage, int dialogImageType, String[] dialogButtonLabels, int defaultIndex) {
super(parentShell, dialogTitle, dialogTitleImage, dialogMessage,
dialogImageType, dialogButtonLabels, defaultIndex);
key = "";
value = "";
}
/**
* Constructor with default values for many fields.
* This is the preferred constructor.
*
* @param shell parent shell
* @param title dialog title
* @param message dialog message
* @param vali validator for the text
*/
public KeyValueInputDialog(Shell shell, String title, String message, IInputValidator vali) {
this(shell, title, null, message, QUESTION, new String[] { IDialogConstants.OK_LABEL, IDialogConstants.CANCEL_LABEL }, 0);
validator = vali;
}
/**
* @return key
*/
public String getKey() {
return key;
}
/**
* @param name key
*/
public void setKey(String name) {
key = name;
}
/**
* @return value
*/
public String getValue() {
return value;
}
/**
* @param val value
*/
public void setValue(String val) {
value = val;
}
/**
* Creates and returns the contents of an area of the dialog which appears
* below the message and above the button bar.
*
* This implementation creates two labels and two textfields.
*
* @param parent parent composite to contain the custom area
* @return the custom area control, or <code>null</code>
*/
protected Control createCustomArea(Composite parent) {
// create the top level composite for the dialog area
Composite composite = new Composite(parent, SWT.NULL);
GridLayout layout = new GridLayout();
layout.numColumns = 2;
composite.setLayout(layout);
composite.setLayoutData(new GridData(GridData.FILL_BOTH));
Label keyLabel = new Label(composite, SWT.LEFT);
keyLabel.setLayoutData(new GridData());
keyLabel.setText("key:");
keyTextField = new Text(composite, SWT.SINGLE | SWT.BORDER);
keyTextField.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
keyTextField.setText(key);
keyTextField.addModifyListener(new ModifyListener() {
public void modifyText(ModifyEvent e) {
if (validator != null) {
key = keyTextField.getText();
if (key != null) {
key = key.trim();
}
String error = validator.isValid(key);
Button ok = getButton(IDialogConstants.OK_ID);
ok.setEnabled(error == null);
} else {
key = keyTextField.getText();
}
}});
Label valueLabel = new Label(composite, SWT.LEFT);
valueLabel.setLayoutData(new GridData());
valueLabel.setText("value:");
valueTextField = new Text(composite, SWT.SINGLE | SWT.BORDER);
valueTextField.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
valueTextField.setText(value);
valueTextField.addModifyListener(new ModifyListener() {
public void modifyText(ModifyEvent e) {
value = valueTextField.getText();
}});
return composite;
}
}
/**
* Dialog to input variables from the current environment.
*/
class EnvVarInputDialog extends MessageDialog {
private List envVarList;
protected int[] selections;
protected String[] items;
/**
* Default constructor from super class. Do not use.
*
* @param parentShell
* @param dialogTitle
* @param dialogTitleImage
* @param dialogMessage
* @param dialogImageType
* @param dialogButtonLabels
* @param defaultIndex
*/
public EnvVarInputDialog(Shell parentShell, String dialogTitle, Image dialogTitleImage, String dialogMessage, int dialogImageType, String[] dialogButtonLabels, int defaultIndex) {
super(parentShell, dialogTitle, dialogTitleImage, dialogMessage,
dialogImageType, dialogButtonLabels, defaultIndex);
selections = new int[0];
items = new String[0];
setShellStyle(getShellStyle() | SWT.RESIZE);
}
/**
* Constructor with default values for many fields.
* This is the preferred way to construct this class.
*
* @param shell parent shell
* @param title dialog title
* @param message dialog message
* @param items items for the list
*/
public EnvVarInputDialog(Shell shell, String title, String message, String[] items) {
this(shell, title, null, message, QUESTION, new String[] { IDialogConstants.OK_LABEL, IDialogConstants.CANCEL_LABEL }, 0);
this.items = items;
}
/**
* Prevents the dialog from appearing too wide and narrow.
*/
protected Point getInitialSize() {
Point p = super.getInitialSize();
int sum = 0;
int maxLen = 0;
for (int i = 0; i < items.length; i++) {
int l = items[i].length();
sum += l;
if (l > maxLen) {
maxLen = l;
}
}
sum /= items.length;
FontData[] fd = envVarList.getFont().getFontData();
p.x = (p.x-100) * sum / maxLen + 100; // scale the list size down by the average length of an item
p.y += fd[0].getHeight() * 8 * 2; // add 8 lines to the default height (*2 to get it right. don't know why this is needed)
p.x *= 2; // don't know why this is needed
return p;
}
/**
* @return indexes of selected items
*/
public int[] getSelections() {
return selections;
}
/**
* Creates and returns the contents of an area of the dialog which appears
* below the message and above the button bar.
*
* This implementation creates two labels and two textfields.
*
* @param parent parent composite to contain the custom area
* @return the custom area control, or <code>null</code>
*/
protected Control createCustomArea(Composite parent) {
Composite composite = new Composite(parent, SWT.NULL);
composite.setLayoutData(new GridData(GridData.FILL_BOTH));
composite.setLayout(new GridLayout());
envVarList = new List(composite, SWT.MULTI | SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL);
envVarList.setLayoutData(new GridData(GridData.FILL_BOTH));
envVarList.setItems(items);
envVarList.addSelectionListener(new SelectionAdapter() {
public void widgetSelected(SelectionEvent event) {
selections = envVarList.getSelectionIndices();
}});
return composite;
}
}
/**
* Creates a new string list field editor.
*
* @param name preference name
* @param labelText text for description label
* @param parent parent component
*/
public KeyValueListFieldEditor(String name, String labelText, Composite parent) {
super(name, labelText, parent);
environment = new HashMap();
}
/**
* Validates the input of the dialog.
* @param newText the contents of the dialog's text field
* @return error message, or null if text is valid
*/
public String isValid(String newText) {
boolean error = false;
for (int i = 0; i < newText.length(); i++) {
if (INVALID_CHARS.indexOf(newText.charAt(i)) >= 0) {
error = true;
break;
}
}
return error ? "invalid character" : null;
}
/**
* @return 2
*/
public int getNumberOfControls() {
return 2;
}
/**
*
* @param numColumns number of columns in the page layout
*/
protected void adjustForNumColumns(int numColumns) {
((GridData)table.getLayoutData()).horizontalSpan = numColumns - 1;
}
/**
* @see org.eclipse.jface.preference.FieldEditor#doFillIntoGrid(org.eclipse.swt.widgets.Composite, int)
* @param parent parent component
* @param numColumns number of columns in the page layout
*/
protected void doFillIntoGrid(Composite parent, int numColumns) {
table = createTable(parent);
((GridData)table.getLayoutData()).horizontalSpan = numColumns - 1;
createButtons(parent);
}
/**
* @see org.eclipse.debug.ui.EnvironmentTab#createEnvironmentTable(org.eclipse.swt.widgets.Composite)
* @param parent parent component
* @return table container
*/
protected Composite createTable(Composite parent) {
Font font = parent.getFont();
// Create table composite
Composite tableComposite = new Composite(parent, SWT.NONE);
GridLayout layout = new GridLayout();
GridData gridData = new GridData(GridData.FILL_BOTH);
gridData.heightHint = 150;
tableComposite.setLayout(layout);
tableComposite.setLayoutData(gridData);
tableComposite.setFont(font);
// Create label
getLabelControl(tableComposite);
// Create table
environmentTable = new TableViewer(tableComposite, SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL | SWT.MULTI | SWT.FULL_SELECTION);
Table table = environmentTable.getTable();
TableLayout tableLayout = new TableLayout();
table.setLayout(tableLayout);
table.setHeaderVisible(true);
table.setFont(font);
gridData = new GridData(GridData.FILL_BOTH);
environmentTable.getControl().setLayoutData(gridData);
environmentTable.setContentProvider(new EnvironmentVariableContentProvider());
environmentTable.setLabelProvider(new EnvironmentVariableLabelProvider());
environmentTable.setColumnProperties(new String[] { ENV_TABLE_KEY, ENV_TABLE_VALUE });
environmentTable.addSelectionChangedListener(new ISelectionChangedListener() {
public void selectionChanged(SelectionChangedEvent event) {
handleTableSelectionChanged(event);
}
});
environmentTable.addDoubleClickListener(new IDoubleClickListener() {
public void doubleClick(DoubleClickEvent event) {
if (!environmentTable.getSelection().isEmpty()) {
handleEnvEditButtonSelected();
}
}
});
// Create columns
ColumnWeightData columnLayout = new ColumnWeightData(50);
tableLayout.addColumnData(columnLayout);
TableColumn tc = new TableColumn(table, SWT.NONE, 0);
tc.setResizable(columnLayout.resizable);
tc.setText(TexlipsePlugin.getResourceString("preferenceKeyValueTableColumn1"));
columnLayout = new ColumnWeightData(50);
tableLayout.addColumnData(columnLayout);
tc = new TableColumn(table, SWT.NONE, 1);
tc.setResizable(columnLayout.resizable);
tc.setText(TexlipsePlugin.getResourceString("preferenceKeyValueTableColumn2"));
return tableComposite;
}
/**
*
* @param parent parent component
* @return button container
*/
protected Composite createButtons(Composite parent) {
Composite buttonComposite = new Composite(parent, SWT.NULL);
buttonComposite.setLayoutData(new GridData(GridData.FILL_VERTICAL));
buttonComposite.setLayout(new GridLayout());
Label empty = new Label(buttonComposite, SWT.NONE);
empty.setLayoutData(new GridData());
envAddButton = new Button(buttonComposite, SWT.PUSH);
envAddButton.setText(TexlipsePlugin.getResourceString("preferenceKeyValueAddButton"));
envAddButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
envAddButton.addSelectionListener(new SelectionAdapter() {
public void widgetSelected(SelectionEvent event) {
handleEnvAddButtonSelected();
}});
envRemoveButton = new Button(buttonComposite, SWT.PUSH);
envRemoveButton.setText(TexlipsePlugin.getResourceString("preferenceKeyValueRemoveButton"));
envRemoveButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
envRemoveButton.addSelectionListener(new SelectionAdapter() {
public void widgetSelected(SelectionEvent event) {
handleEnvRemoveButtonSelected();
}});
envRemoveButton.setEnabled(false);
envEditButton = new Button(buttonComposite, SWT.PUSH);
envEditButton.setText(TexlipsePlugin.getResourceString("preferenceKeyValueEditButton"));
envEditButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
envEditButton.addSelectionListener(new SelectionAdapter() {
public void widgetSelected(SelectionEvent event) {
handleEnvEditButtonSelected();
}});
envEditButton.setEnabled(false);
envImportButton = new Button(buttonComposite, SWT.PUSH);
envImportButton.setText(TexlipsePlugin.getResourceString("preferenceKeyValueImportButton"));
envImportButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
envImportButton.addSelectionListener(new SelectionAdapter() {
public void widgetSelected(SelectionEvent event) {
handleEnvImportButtonSelected();
}});
Label filler = new Label(buttonComposite, SWT.NONE);
filler.setLayoutData(new GridData(GridData.FILL_VERTICAL));
return buttonComposite;
}
/**
* Handle add-button presses.
*/
private void handleEnvAddButtonSelected() {
KeyValueInputDialog dialog =
new KeyValueInputDialog(getLabelControl().getShell(),
TexlipsePlugin.getResourceString("preferenceKeyValueAddDialogTitle"),
TexlipsePlugin.getResourceString("preferenceKeyValueAddDialogLabel"), this);
int code = dialog.open();
if (code == KeyValueInputDialog.OK) {
String key = dialog.getKey();
if (key == null || key.length() == 0) {
return;
}
String value = dialog.getValue();
environment.put(key, value);
environmentTable.add(new EnvironmentVariable(key, value));
}
}
/**
* Handle remove-button presses.
*/
private void handleEnvRemoveButtonSelected() {
IStructuredSelection sel = (IStructuredSelection) environmentTable.getSelection();
environmentTable.getControl().setRedraw(false);
for (Iterator i = sel.iterator(); i.hasNext(); ) {
EnvironmentVariable var = (EnvironmentVariable) i.next();
environmentTable.remove(var);
environment.remove(var.getName());
}
environmentTable.getControl().setRedraw(true);
}
/**
* Handle edit-button presses.
*/
private void handleEnvEditButtonSelected() {
IStructuredSelection sel = (IStructuredSelection) environmentTable.getSelection();
EnvironmentVariable var = (EnvironmentVariable) sel.iterator().next();
if (var == null) {
return;
}
KeyValueInputDialog dialog =
new KeyValueInputDialog(getLabelControl().getShell(),
TexlipsePlugin.getResourceString("preferenceKeyValueEditDialogTitle"),
TexlipsePlugin.getResourceString("preferenceKeyValueEditDialogLabel"), this);
dialog.setKey(var.getName());
dialog.setValue(var.getValue());
int code = dialog.open();
if (code == KeyValueInputDialog.OK) {
String key = dialog.getKey();
if (key == null || key.length() == 0) {
return;
}
String value = dialog.getValue();
environmentTable.remove(var);
environment.remove(var.getName());
environment.put(key, value);
environmentTable.add(new EnvironmentVariable(key, value));
}
}
/**
* Handle import-button presses.
*/
private void handleEnvImportButtonSelected() {
String[] items = PathUtils.getStrings(PathUtils.getEnv());
EnvVarInputDialog dialog =
new EnvVarInputDialog(getLabelControl().getShell(),
TexlipsePlugin.getResourceString("preferenceEnvVarImportDialogTitle"),
TexlipsePlugin.getResourceString("preferenceEnvVarImportDialogLabel"), items);
int code = dialog.open();
if (code == EnvVarInputDialog.OK) {
int[] sel = dialog.getSelections();
for (int i = 0; i < sel.length; i++) {
String line = items[sel[i]];
int index = line.indexOf('=');
if (index > 0) {
String key = line.substring(0, index);
String value = line.substring(index+1);
environment.put(key, value);
environmentTable.add(new EnvironmentVariable(key, value));
}
}
}
}
/**
* Responds to a selection changed event in the environment table
* @param event the selection change event
*/
protected void handleTableSelectionChanged(SelectionChangedEvent event) {
int size = ((IStructuredSelection)event.getSelection()).size();
envEditButton.setEnabled(size == 1);
envRemoveButton.setEnabled(size > 0);
}
/**
* Loads the environment variables from preference store.
*/
protected void doLoad() {
doLoadFrom(getPreferenceStore().getString(getPreferenceName()));
}
/**
* Loads the environment variables from the given string.
* @param str
*/
protected void doLoadFrom(String str) {
environment.clear();
if (str == null) {
return;
}
String[] binds = str.split(SEPARATOR);
if (binds == null) {
return;
}
for (int i = 0; i < binds.length; i++) {
int index = binds[i].indexOf('=');
if (index <= 0) {
continue;
}
environment.put(binds[i].substring(0, index),
binds[i].substring(index+1));
}
environmentTable.setInput(environment);
}
/**
* Loads the default environment variables from preference store.
*/
protected void doLoadDefault() {
doLoadFrom(getPreferenceStore().getDefaultString(getPreferenceName()));
}
/**
* Stores the environment variables to single string.
*/
protected void doStore() {
StringBuffer sb = new StringBuffer();
String[] keys = (String[]) environment.keySet().toArray(new String[0]);
for (int i = 0; i < keys.length; i++) {
sb.append(keys[i]);
sb.append('=');
sb.append(environment.get(keys[i]));
if (i < keys.length-1) {
sb.append(',');
}
}
getPreferenceStore().setValue(getPreferenceName(), sb.toString());
}
}