/*******************************************************************************
 * Copyright (c) 2004, 2018 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.swt.tools.internal;

import java.io.*;
import java.lang.reflect.*;

import org.eclipse.swt.*;
import org.eclipse.swt.custom.*;
import org.eclipse.swt.graphics.*;
import org.eclipse.swt.layout.*;
import org.eclipse.swt.widgets.*;

public class JNIGeneratorAppUI {

	Display display;
	Shell shell;
	
	Composite actionsPanel;
	Combo mainClassCb, outputDirCb;
	Table classesLt, membersLt, paramsLt;
	ProgressBar progressBar;
	Label progressLabel;
	FileDialog fileDialog;
	
	TableEditor paramTextEditor, memberTextEditor, classTextEditor;
	FlagsEditor paramListEditor, memberListEditor, classListEditor;
	Text paramEditorTx, memberEditorTx, classEditorTx;
	List paramEditorLt, memberEditorLt, classEditorLt;
	
	static class FlagsEditor {
		Table parent;
		int column = -1;
		TableItem item;
		
		public FlagsEditor(Table parent) {
			this.parent = parent;
		}
		
		public int getColumn() {
			return column;
		}
		
		public TableItem getItem() {
			return item;
		}
		
		public void setColumn(int column) {
			this.column = column;
		}
		
		public void setItem(TableItem item) {
			this.item = item;
		}
	}
	
	JNIGeneratorApp app;

	static final int CLASS_NAME_COLUMN = 0;
	static final int CLASS_FLAGS_COLUMN = 1;
	static final int CLASS_EXCLUDE_COLUMN = 2;
	
	static final int FIELD_NAME_COLUMN = 0;
	static final int FIELD_FLAGS_COLUMN = 1;
	static final int FIELD_CAST_COLUMN = 2;
	static final int FIELD_ACCESSOR_COLUMN = 3;
	static final int FIELD_EXCLUDE_COLUMN = 4;
	
	static final int METHOD_NAME_COLUMN = 0;
	static final int METHOD_FLAGS_COLUMN = 1;
	static final int METHOD_ACCESSOR_COLUMN = 2;
	static final int METHOD_EXCLUDE_COLUMN = 3;
	
	static final int PARAM_INDEX_COLUMN = 0;
	static final int PARAM_TYPE_COLUMN = 1;
	static final int PARAM_FLAGS_COLUMN = 2;
	static final int PARAM_CAST_COLUMN = 3;
	
public JNIGeneratorAppUI() {
	this (new JNIGeneratorApp());
}

public JNIGeneratorAppUI(JNIGeneratorApp app) {
	this.app = app;
}

void cleanup() {
	display.dispose();
}

void generateStructsHeader () {
	StructsGenerator gen = new StructsGenerator(true);
	gen.setMainClass(app.getMainClass());
	gen.setMetaData(app.getMetaData());
	gen.setClasses(getSelectedClasses());
	gen.generate();
}

void generateStructs () {
	StructsGenerator gen = new StructsGenerator(false);
	gen.setMainClass(app.getMainClass());
	gen.setMetaData(app.getMetaData());
	gen.setClasses(getSelectedClasses());
	gen.generate();
}

void generateSizeof () {
	SizeofGenerator gen = new SizeofGenerator();
	gen.setMainClass(app.getMainClass());
	gen.setMetaData(app.getMetaData());
	gen.setClasses(getSelectedClasses());
	gen.generate();
}

void generateMetaData () {
	MetaDataGenerator gen = new MetaDataGenerator();
	gen.setMainClass(app.getMainClass());
	gen.setMetaData(app.getMetaData());
	JNIMethod[] methods = getSelectedMethods();
	if (methods.length != 0) {
		gen.generate(methods);
	} else {
		gen.setClasses(getSelectedClasses());
		gen.generate();
	}
}

void generateNatives () {
	NativesGenerator gen = new NativesGenerator();
	gen.setMainClass(app.getMainClass());
	gen.setMetaData(app.getMetaData());
	JNIMethod[] methods = getSelectedMethods();
	if (methods.length != 0) {
		gen.generate(methods);
	} else {
		gen.setClasses(getSelectedClasses());
		gen.generate();
	}
}

void generateAll() {
	if (!updateOutputDir()) return;
	Cursor cursor = display.getSystemCursor(SWT.CURSOR_WAIT);
	shell.setCursor(cursor);
	shell.setEnabled(false);
	Control[] children = actionsPanel.getChildren();
	for (int i = 0; i < children.length; i++) {
		Control child = children[i];
		if (child instanceof Button) child.setEnabled(false);				
	}
	boolean showProgress = true;
	final boolean finalShowProgress = showProgress; /* avoid dead code warning below */
	if (showProgress) {
		progressLabel.setText("");
		progressBar.setSelection(0);
		progressLabel.setVisible(true);
		progressBar.setVisible(true);
	}
	final boolean[] done = new boolean[1];
	new Thread() {
		@Override
		public void run() {
			try {
				app.generate(!finalShowProgress ? null : new ProgressMonitor() {
					int total, step, maximum = 100;
					@Override
					public void setTotal(final int total) {
						this.total = total;
						display.syncExec(() -> progressBar.setMaximum(maximum));
					}
					@Override
					public void step() {
						int oldValue = step * maximum / total;
						step++;
						final int newValue = step * maximum / total;
						if (oldValue == newValue) return;
						display.syncExec(() -> progressBar.setSelection(newValue));					
					}
					@Override
					public void setMessage(final String message) {
						display.syncExec(() -> {
							progressLabel.setText(message);
							progressLabel.update();
						});
					}
				});
			} finally {
				done[0] = true;
				display.wake();
			}
		}
	}.start();
	while (!done[0]) {
		if (!display.readAndDispatch()) display.sleep();
	}
	for (int i = 0; i < children.length; i++) {
		Control child = children[i];
		if (child instanceof Button) child.setEnabled(true);				
	}
	if (showProgress) {
		progressBar.setVisible(false);
		progressLabel.setVisible(false);
	}
	shell.setEnabled(true);
	shell.setCursor(null);
}

void generateConstants () {
	ConstantsGenerator gen = new ConstantsGenerator();
	gen.setMainClass(app.getMainClass());
	gen.setMetaData(app.getMetaData());
	JNIField[] fields = getSelectedFields();
	if (fields.length != 0) {
		gen.generate(fields);
	} else {
		gen.setClasses(getSelectedClasses());
		gen.generate();
	}
}

JNIClass[] getSelectedClasses() {
	TableItem[] items = classesLt.getSelection();
	JNIClass[] classes = new JNIClass[items.length];
	for (int i = 0; i < items.length; i++) {
		TableItem item = items[i];
		classes[i] = (JNIClass)item.getData();
	}
	return classes;
}

JNIMethod[] getSelectedMethods() {
	TableItem[] selection = membersLt.getSelection();
	JNIMethod[] methods = new JNIMethod[selection.length];
	int count = 0;
	for (int i = 0; i < selection.length; i++) {
		TableItem item = selection [i];
		Object data = item.getData();
		if (data instanceof JNIMethod) {
			methods[count++] = (JNIMethod)data;
		}
	}
	if (count != methods.length) {
		JNIMethod[] result = new JNIMethod[count];
		System.arraycopy(methods, 0, result, 0, count);
		methods = result;
	}
	return methods;
}

JNIField[] getSelectedFields() {
	TableItem[] selection = membersLt.getSelection();
	JNIField[] fields = new JNIField[selection.length];
	int count = 0;
	for (int i = 0; i < selection.length; i++) {
		TableItem item = selection [i];
		Object data = item.getData();
		if (data instanceof JNIField) {
			fields[count++] = (JNIField)data;
		}
	}
	if (count != fields.length) {
		JNIField[] result = new JNIField[count];
		System.arraycopy(fields, 0, result, 0, count);
		fields = result;
	}
	return fields;
}

public void open () {
	display = new Display();
	shell = new Shell(display);
	shell.setText("JNI Generator");

	GridData data;
	GridLayout shellLayout = new GridLayout();
	shellLayout.numColumns = 2;
	shell.setLayout(shellLayout);
	
	Composite panel = new Composite(shell, SWT.NONE);
	data = new GridData(GridData.FILL_BOTH);
	panel.setLayoutData(data);
	
	GridLayout panelLayout = new GridLayout();
	panelLayout.numColumns = 1;
	panel.setLayout(panelLayout);
	
	Listener updateMainClassListener =  e -> {
		updateMainClass();
		if (!updateOutputDir()) return;
		updateClasses();
		updateMembers();
		updateParameters();
	};	
	createMainClassPanel(panel, updateMainClassListener);
	createClassesPanel(panel);
	createMembersPanel(panel);
	createParametersPanel(panel);
	createActionButtons(shell);

	Point preferredSize = shell.computeSize(SWT.DEFAULT, SWT.DEFAULT);
	shell.setSize(shell.getSize().x, preferredSize.y);
	
	updateMainClass();
	updateClasses();
	updateMembers();
	updateParameters();
}

void createMainClassPanel(Composite panel, Listener updateListener) {
	Label mainClassLb = new Label(panel, SWT.NONE);
	mainClassLb.setText("&Main Class:");

	GridData data;
	mainClassCb = new Combo(panel, SWT.DROP_DOWN);
	String mainClass = app.getMainClassName();
	mainClassCb.setText(mainClass == null ? "" : mainClass);
	data = new GridData(GridData.FILL_HORIZONTAL);
	mainClassCb.setLayoutData(data);
	mainClassCb.addListener(SWT.Selection, updateListener);
	mainClassCb.addListener(SWT.DefaultSelection, updateListener);

	Label outputDirLb = new Label(panel, SWT.NONE);
	outputDirLb.setText("&Output Dir:");
	
	outputDirCb = new Combo(panel, SWT.DROP_DOWN);
	String outputDir = app.getOutputDir();
	outputDirCb.setText(outputDir == null ? "" : outputDir);
	data = new GridData(GridData.FILL_HORIZONTAL);
	outputDirCb.setLayoutData(data);
	outputDirCb.addListener(SWT.Selection, updateListener);
	outputDirCb.addListener(SWT.DefaultSelection, updateListener);

	String mainClasses = app.getMetaData().getMetaData("swt_main_classes", null);
	if (mainClasses != null) {
		String[] list = JNIGenerator.split(mainClasses, ",");
		for (int i = 0; i < list.length; i += 2) {
			String className = list[i].trim();
			try {
				Class.forName(className, false, getClass().getClassLoader());
				mainClassCb.add(className);
				outputDirCb.add(list[i + 1].trim());
			} catch (Exception e) {}
		}
	}
}

void createClassesPanel(Composite panel) {
	Label classesLb = new Label(panel, SWT.NONE);
	classesLb.setText("&Classes:");

	GridData data;
	classesLt = new Table(panel, SWT.CHECK | SWT.MULTI | SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL | SWT.FULL_SELECTION);
	data = new GridData(GridData.FILL_BOTH);
	data.heightHint = classesLt.getItemHeight() * 6;
	classesLt.setLayoutData(data);
	classesLt.setHeaderVisible(true);
	classesLt.addListener(SWT.Selection, e -> {
		if (e.detail == SWT.CHECK) {
			updateGenerate((TableItem)e.item);
		} else {
			updateMembers();
			updateParameters();
		}
	});
	TableColumn column;
	column = new TableColumn(classesLt, SWT.NONE, CLASS_NAME_COLUMN);
	column.setText("Class");
	column = new TableColumn(classesLt, SWT.NONE, CLASS_FLAGS_COLUMN);
	column.setText("Flags");
	/*
	column = new TableColumn(classesLt, SWT.NONE, CLASS_EXCLUDE_COLUMN);
	column.setText("Exclude");
	*/
	
	classTextEditor = new TableEditor(classesLt);
	classTextEditor.grabHorizontal = true;
	classEditorTx = new Text(classesLt, SWT.SINGLE);
	classTextEditor.setEditor(classEditorTx);
	Listener classTextListener = e -> {
		if (e.type == SWT.Traverse) {
			switch (e.detail) {
				case SWT.TRAVERSE_ESCAPE:
					classTextEditor.setItem(null);
					break;
				default:
					return;
			}
		}
		classEditorTx.setVisible(false);
		TableItem item = classTextEditor.getItem();
		if (item == null) return;
		int column1 = classTextEditor.getColumn();
		JNIClass clazz = (JNIClass)item.getData();
		if (column1 == CLASS_EXCLUDE_COLUMN) {
			String text = classEditorTx.getText();
			clazz.setExclude(text);
			item.setText(column1, clazz.getExclude());
			classesLt.getColumn(column1).pack();
		}
	};
	classEditorTx.addListener(SWT.DefaultSelection, classTextListener);
	classEditorTx.addListener(SWT.FocusOut, classTextListener);
	classEditorTx.addListener(SWT.Traverse, classTextListener);
	
	final Shell floater = new Shell(shell, SWT.NO_TRIM);
	floater.setLayout(new FillLayout());
	classListEditor = new FlagsEditor(classesLt);
	classEditorLt = new List(floater, SWT.MULTI | SWT.BORDER);
	classEditorLt.setItems(JNIClass.FLAGS);
	floater.pack();
	floater.addListener(SWT.Close, e -> {
		classListEditor.setItem(null);
		e.doit = false;
		floater.setVisible(false);
	});
	Listener classesListListener = e -> {
		if (e.type == SWT.Traverse) {
			switch (e.detail) {
				case SWT.TRAVERSE_RETURN:
					break;
				default:
					return;
			}
		}
		floater.setVisible(false);
		TableItem item = classListEditor.getItem();
		if (item == null) return;
		int column1 = classListEditor.getColumn();
		JNIClass clazz = (JNIClass)item.getData();
		if (column1 == CLASS_FLAGS_COLUMN) {
			String[] flags = classEditorLt.getSelection();
			clazz.setFlags(flags);
			item.setText(column1, getFlagsString(clazz.getFlags()));
			item.setChecked(clazz.getGenerate());
			classesLt.getColumn(column1).pack();
		}
	};
	classEditorLt.addListener(SWT.DefaultSelection, classesListListener);
	classEditorLt.addListener(SWT.FocusOut, classesListListener);
	classEditorLt.addListener(SWT.Traverse, classesListListener);

	classesLt.addListener(SWT.MouseDown, e -> e.display.asyncExec (() -> {
		if (classesLt.isDisposed ()) return;
		if (e.button != 1) return;
		Point pt = new Point(e.x, e.y);
		TableItem item = classesLt.getItem(pt);
		if (item == null) return;
		int column1 = -1;
		for (int i = 0; i < classesLt.getColumnCount(); i++) {
			if (item.getBounds(i).contains(pt)) {
				column1 = i;
				break;
			}				
		}
		if (column1 == -1) return;
		JNIClass data1 = (JNIClass)item.getData();
		if (column1 == CLASS_EXCLUDE_COLUMN) {
			classTextEditor.setColumn(column1);
			classTextEditor.setItem(item);
			classEditorTx.setText(data1.getExclude());
			classEditorTx.selectAll();
			classEditorTx.setVisible(true);
			classEditorTx.setFocus();
		} else if (column1 == CLASS_FLAGS_COLUMN) {
			classListEditor.setColumn(column1);
			classListEditor.setItem(item);
			classEditorLt.setSelection(data1.getFlags());
			floater.setLocation(classesLt.toDisplay(e.x, e.y));
			floater.setVisible(true);
			classEditorLt.setFocus();
		}
	}));
}

void createMembersPanel(Composite panel) {
	GridData data;
	Composite comp = new Composite(panel, SWT.NONE);
	data = new GridData(GridData.FILL_HORIZONTAL);
	comp.setLayoutData(data);	
	GridLayout layout = new GridLayout(2, false);
	layout.marginWidth = layout.marginHeight = 0;
	comp.setLayout(layout);
	Label membersLb = new Label(comp, SWT.NONE);
	membersLb.setText("Mem&bers [regex]:");
	final Text searchText = new Text(comp, SWT.SINGLE | SWT.SEARCH);
	searchText.setText(".*");
	data = new GridData(GridData.FILL_HORIZONTAL);
	searchText.setLayoutData(data);
	searchText.addListener(SWT.DefaultSelection, new Listener() {
		boolean match (int index, String pattern) {
			TableItem item = membersLt.getItem(index);
			String text = item.getText();
			try {
				if (text.matches(pattern)) {
					membersLt.setSelection(index);
					return true;
				}
			} catch (Exception ex) {}
			return false;
		}
		@Override
		public void handleEvent(Event e) {
			String pattern = searchText.getText();
			int selection = membersLt.getSelectionIndex();
			int count = membersLt.getItemCount();
			selection++;
			for (int i = selection; i < count; i++) {
				if (match (i, pattern)) return;
			}
			for (int i = 0; i < selection; i++) {
				if (match (i, pattern)) return;
			}
		}
	});

	membersLt = new Table(panel, SWT.CHECK | SWT.MULTI | SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL | SWT.FULL_SELECTION);
	data = new GridData(GridData.FILL_BOTH);
	data.heightHint = membersLt.getItemHeight() * 6;
	membersLt.setLayoutData(data);
	membersLt.addListener(SWT.Selection, e -> {
		if (e.detail == SWT.CHECK) {
			updateGenerate((TableItem)e.item);
		} else {
			updateParameters();
		}
	});
	
	memberTextEditor = new TableEditor(membersLt);
	memberTextEditor.grabHorizontal = true;
	memberEditorTx = new Text(membersLt, SWT.SINGLE);
	memberTextEditor.setEditor(memberEditorTx);
	Listener memberTextListener = e -> {
		if (e.type == SWT.Traverse) {
			switch (e.detail) {
				case SWT.TRAVERSE_ESCAPE:
					memberTextEditor.setItem(null);
					break;
				default:
					return;
			}
		}
		memberEditorTx.setVisible(false);
		TableItem item = memberTextEditor.getItem();
		if (item == null) return;
		int column = memberTextEditor.getColumn();
		JNIItem memberData = (JNIItem)item.getData();
		String text = memberEditorTx.getText();
		if (memberData instanceof JNIField) {
			JNIField field = (JNIField)memberData;
			switch (column) {
				case FIELD_CAST_COLUMN: {
					field.setCast(text);
					item.setText(column, field.getCast());
					break;
				}
				case FIELD_ACCESSOR_COLUMN: {
					field.setAccessor(text.equals(field.getName()) ? "" : text);
					item.setText(column, field.getAccessor());
					break;
				}
				case FIELD_EXCLUDE_COLUMN: {
					field.setExclude(text);
					item.setText(column, field.getExclude());
					break;
				}
			}
			membersLt.getColumn(column).pack();
		} else if (memberData instanceof JNIMethod) {
			JNIMethod method = (JNIMethod)memberData;
			switch (column) {
				case METHOD_ACCESSOR_COLUMN: {
					method.setAccessor(text.equals(method.getName()) ? "" : text);
					item.setText(column, method.getAccessor());
					break;
				}
				case METHOD_EXCLUDE_COLUMN: {
					method.setExclude(text);
					item.setText(column, method.getExclude());
					break;
				}
			}
			membersLt.getColumn(column).pack();
		}
	};
	memberEditorTx.addListener(SWT.DefaultSelection, memberTextListener);
	memberEditorTx.addListener(SWT.FocusOut, memberTextListener);
	memberEditorTx.addListener(SWT.Traverse, memberTextListener);
	
	final Shell floater = new Shell(shell, SWT.NO_TRIM);
	floater.setLayout(new FillLayout());
	memberListEditor = new FlagsEditor(membersLt);
	memberEditorLt = new List(floater, SWT.MULTI | SWT.BORDER);
	floater.addListener(SWT.Close, e -> {
		memberListEditor.setItem(null);
		e.doit = false;
		floater.setVisible(false);
	});
	Listener memberListListener = e -> {
		if (e.type == SWT.Traverse) {
			switch (e.detail) {
				case SWT.TRAVERSE_RETURN:
					break;
				default:
					return;
			}
		}
		floater.setVisible(false);
		TableItem item = memberListEditor.getItem();
		if (item == null) return;
		int column = memberListEditor.getColumn();
		JNIItem data1 = (JNIItem)item.getData();
		String[] flags = memberEditorLt.getSelection();
		data1.setFlags(flags);
		item.setText(column, getFlagsString(data1.getFlags()));
		item.setChecked(data1.getGenerate());
		membersLt.getColumn(column).pack();
	};
	memberEditorLt.addListener(SWT.DefaultSelection, memberListListener);
	memberEditorLt.addListener(SWT.FocusOut, memberListListener);
	memberEditorLt.addListener(SWT.Traverse, memberListListener);
	
	membersLt.addListener(SWT.MouseDown, e -> e.display.asyncExec (() -> {
		if (membersLt.isDisposed ()) return;
		if (e.button != 1) return;
		Point pt = new Point(e.x, e.y);
		TableItem item = membersLt.getItem(pt);
		if (item == null) return;
		int column = -1;
		for (int i = 0; i < membersLt.getColumnCount(); i++) {
			if (item.getBounds(i).contains(pt)) {
				column = i;
				break;
			}				
		}
		if (column == -1) return;
		Object itemData = item.getData();
		if (itemData instanceof JNIField) {
			JNIField field = (JNIField)itemData;
			if (column == FIELD_CAST_COLUMN || column == FIELD_ACCESSOR_COLUMN || column == FIELD_EXCLUDE_COLUMN) {
				memberTextEditor.setColumn(column);
				memberTextEditor.setItem(item);
				String text1 = "";
				switch (column) {
					case FIELD_CAST_COLUMN: text1 = field.getCast(); break;
					case FIELD_ACCESSOR_COLUMN: {
						text1 = field.getAccessor(); 
						if (text1.length() == 0) {
							text1 = field.getName();
							int index = text1.lastIndexOf('_');
							if (index != -1) {
								char[] chars = text1.toCharArray();
								chars[index] = '.';
								text1 = new String(chars);
							}
						}
						break;
					}
					case FIELD_EXCLUDE_COLUMN: text1 = field.getExclude(); break;
				}
				memberEditorTx.setText(text1);
				memberEditorTx.selectAll();
				memberEditorTx.setVisible(true);
				memberEditorTx.setFocus();
			} else if (column == FIELD_FLAGS_COLUMN) {
				memberListEditor.setColumn(column);
				memberListEditor.setItem(item);
				memberEditorLt.setItems(JNIField.FLAGS);
				memberEditorLt.setSelection(field.getFlags());
				floater.setLocation(membersLt.toDisplay(e.x, e.y));
				floater.pack();
				floater.setVisible(true);
				memberEditorLt.setFocus();
			}
		} else if (itemData instanceof JNIMethod) {
			JNIMethod method = (JNIMethod)itemData;
			if (column == METHOD_EXCLUDE_COLUMN || column == METHOD_ACCESSOR_COLUMN) {
				memberTextEditor.setColumn(column);
				memberTextEditor.setItem(item);
				String text2 = "";
				switch (column) {
					case METHOD_ACCESSOR_COLUMN: {
						text2 = method.getAccessor();
						if (text2.length() == 0) text2 = method.getName();
						break;
					}
					case METHOD_EXCLUDE_COLUMN: text2 = method.getExclude(); break;
				}
				memberEditorTx.setText(text2);
				memberEditorTx.selectAll();
				memberEditorTx.setVisible(true);
				memberEditorTx.setFocus();
			} else if (column == METHOD_FLAGS_COLUMN) {
				memberListEditor.setColumn(column);
				memberListEditor.setItem(item);
				memberEditorLt.setItems(JNIMethod.FLAGS);
				memberEditorLt.setSelection(method.getFlags());
				floater.setLocation(membersLt.toDisplay(e.x, e.y));
				floater.pack();
				floater.setVisible(true);
				memberEditorLt.setFocus();
			}
		}
	}));
}

void createParametersPanel(Composite panel) {
	Label paramsLb = new Label(panel, SWT.NONE);
	paramsLb.setText("&Parameters:");
	
	GridData data;
	paramsLt = new Table(panel, SWT.MULTI | SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL | SWT.FULL_SELECTION);
	data = new GridData(GridData.FILL_BOTH);
	int itemHeight = paramsLt.getItemHeight();
	data.heightHint = itemHeight * 6;
	paramsLt.setLayoutData(data);
	paramsLt.addListener(SWT.Selection, e -> {
		if (e.detail == SWT.CHECK) {
			updateGenerate((TableItem)e.item);
		}
	});

	TableColumn column;
	column = new TableColumn(paramsLt, SWT.NONE, PARAM_INDEX_COLUMN);
	column = new TableColumn(paramsLt, SWT.NONE, PARAM_TYPE_COLUMN);
	column.setText("Type");
	column = new TableColumn(paramsLt, SWT.NONE, PARAM_FLAGS_COLUMN);
	column.setText("Flags");
	column = new TableColumn(paramsLt, SWT.NONE, PARAM_CAST_COLUMN);
	column.setText("Cast");
	
	paramTextEditor = new TableEditor(paramsLt);
	paramTextEditor.grabHorizontal = true;
	paramEditorTx = new Text(paramsLt, SWT.SINGLE);
	paramTextEditor.setEditor(paramEditorTx);
	Listener paramTextListener = e -> {
		if (e.type == SWT.Traverse) {
			switch (e.detail) {
				case SWT.TRAVERSE_ESCAPE:
					paramTextEditor.setItem(null);
					break;
				default:
					return;
			}
		}
		paramEditorTx.setVisible(false);
		TableItem item = paramTextEditor.getItem();
		if (item == null) return;
		int column1 = paramTextEditor.getColumn();
		JNIParameter param = (JNIParameter)item.getData();
		if (column1 == PARAM_CAST_COLUMN) {
			String text = paramEditorTx.getText();
			param.setCast(text);
			item.setText(column1, param.getCast());
			paramsLt.getColumn(column1).pack();
		}
	};
	paramEditorTx.addListener(SWT.DefaultSelection, paramTextListener);
	paramEditorTx.addListener(SWT.FocusOut, paramTextListener);
	paramEditorTx.addListener(SWT.Traverse, paramTextListener);
	
	final Shell floater = new Shell(shell, SWT.NO_TRIM);
	floater.setLayout(new FillLayout());
	paramListEditor = new FlagsEditor(paramsLt);
	paramEditorLt = new List(floater, SWT.MULTI | SWT.BORDER);
	paramEditorLt.setItems(JNIParameter.FLAGS);
	floater.pack();
	floater.addListener(SWT.Close, e -> {
		paramListEditor.setItem(null);
		e.doit = false;
		floater.setVisible(false);
	});
	Listener paramListListener = e -> {
		if (e.type == SWT.Traverse) {
			switch (e.detail) {
				case SWT.TRAVERSE_RETURN:
					break;
				default:
					return;
			}
		}
		floater.setVisible(false);
		TableItem item = paramListEditor.getItem();
		if (item == null) return;
		int column1 = paramListEditor.getColumn();
		JNIParameter param = (JNIParameter)item.getData();
		if (column1 == PARAM_FLAGS_COLUMN) {
			String[] flags = paramEditorLt.getSelection();
			param.setFlags(flags);
			item.setText(column1, getFlagsString(param.getFlags()));
			paramsLt.getColumn(column1).pack();
		}
	};
	paramEditorLt.addListener(SWT.DefaultSelection, paramListListener);
	paramEditorLt.addListener(SWT.FocusOut, paramListListener);
	paramEditorLt.addListener(SWT.Traverse, paramListListener);

	paramsLt.addListener(SWT.MouseDown, e -> e.display.asyncExec (() -> {
		if (paramsLt.isDisposed ()) return;
		if (e.button != 1) return;
		Point pt = new Point(e.x, e.y);
		TableItem item = paramsLt.getItem(pt);
		if (item == null) return;
		int column1 = -1;
		for (int i = 0; i < paramsLt.getColumnCount(); i++) {
			if (item.getBounds(i).contains(pt)) {
				column1 = i;
				break;
			}				
		}
		if (column1 == -1) return;
		JNIParameter param = (JNIParameter)item.getData();
		if (column1 == PARAM_CAST_COLUMN) {
			paramTextEditor.setColumn(column1);
			paramTextEditor.setItem(item);
			paramEditorTx.setText(param.getCast());
			paramEditorTx.selectAll();
			paramEditorTx.setVisible(true);
			paramEditorTx.setFocus();
		} else if (column1 == PARAM_FLAGS_COLUMN) {
			paramListEditor.setColumn(column1);
			paramListEditor.setItem(item);
			paramEditorLt.setSelection(param.getFlags());
			floater.setLocation(paramsLt.toDisplay(e.x, e.y));
			floater.setVisible(true);
			paramEditorLt.setFocus();
		}
	}));
}

Button createActionButton(Composite parent, String text, Listener listener) {
	Button action = new Button(parent, SWT.PUSH);
	action.setText(text);
	GridData data = new GridData(GridData.FILL_HORIZONTAL);
	action.setLayoutData(data);
	action.addListener(SWT.Selection, listener);
	return action;
}

void createActionButtons(Composite parent) {		
	actionsPanel = new Composite(parent, SWT.NONE);

	GridData data = new GridData(GridData.FILL_VERTICAL);
	actionsPanel.setLayoutData(data);
		
	GridLayout actionsLayout = new GridLayout();
	actionsLayout.numColumns = 1;
	actionsPanel.setLayout(actionsLayout);
	
	createActionButton(actionsPanel, "Generate &All", e -> generateAll());
	
	Label separator = new Label(actionsPanel, SWT.SEPARATOR | SWT.HORIZONTAL);
	data = new GridData(GridData.FILL_HORIZONTAL);
	separator.setLayoutData(data);
	separator = new Label(actionsPanel, SWT.SEPARATOR | SWT.HORIZONTAL);
	data = new GridData(GridData.FILL_HORIZONTAL);
	separator.setLayoutData(data);
	
	createActionButton(actionsPanel, "Generate Structs &Header", e -> generateStructsHeader());
	createActionButton(actionsPanel, "Generate &Structs", e -> generateStructs());
	createActionButton(actionsPanel, "Generate &Natives", e -> generateNatives());
	createActionButton(actionsPanel, "Generate Meta &Data", e -> generateMetaData());
	createActionButton(actionsPanel, "Generate Cons&tants", e -> generateConstants());	
	createActionButton(actionsPanel, "Generate Si&zeof", e -> generateSizeof());

	Composite filler = new Composite(actionsPanel, SWT.NONE);
	filler.setLayoutData(new GridData(GridData.FILL_BOTH));
	
	progressLabel = new Label(actionsPanel, SWT.NONE);
	data = new GridData(GridData.FILL_HORIZONTAL);
	progressLabel.setLayoutData(data);
	progressLabel.setVisible(false);
	
	progressBar = new ProgressBar(actionsPanel, SWT.NONE);
	data = new GridData(GridData.FILL_HORIZONTAL);
	progressBar.setLayoutData(data);
	progressBar.setVisible(false);
}

public void run() {
	shell.open();
	MessageBox box = new MessageBox(shell, SWT.YES | SWT.NO);
	box.setText("Warning");
	box.setMessage("This tool is obsolete as of Eclipse 3.5 M2.\nThe meta data has been embedded in java source files.\nThere is a new plugin tool that replaces this tool.\nSee http://www.eclipse.org/swt/jnigen.php.\n\n Continue?");
	int result = box.open();
	if (result == SWT.NO) {
		shell.dispose();
	}
	while (!shell.isDisposed()) {
		if (!display.readAndDispatch()) display.sleep ();
	}
	cleanup();
}

String getPackageString() {
	int dot = app.getMainClassName().lastIndexOf('.');
	if (dot == -1) return "";
	return app.getMainClassName().substring(0, dot);
}

String getClassString(JNIType type) {
	String name = type.getTypeSignature3(false);
	int index = name.lastIndexOf('.');
	if (index == -1) return name;
	return name.substring(index + 1, name.length());
}

String getFlagsString(String[] flags) {
	if (flags.length == 0) return "";
	StringBuilder buffer = new StringBuilder();
	for (int j = 0; j < flags.length; j++) {
		String flag = flags[j];
		if (buffer.length() != 0) buffer.append(", ");
		buffer.append(flag);
	}
	return buffer.toString();
}

String getMethodString(JNIMethod method) {
	String pkgName = getPackageString();
	StringBuilder buffer = new StringBuilder();
	buffer.append(method.getName());
	buffer.append("(");
	JNIParameter[] params = method.getParameters();
	for (int i = 0; i < params.length; i++) {
		JNIParameter param = params[i];
		if (i != 0) buffer.append(",");
		String string = param.getType().getTypeSignature3(false);
		if (string.startsWith(pkgName)) string = string.substring(pkgName.length() + 1);
		buffer.append(string);
	}
	buffer.append(")");
	return buffer.toString();
}

String getFieldString(JNIField field) {
	return field.getName();
}

void updateClasses() {
	classesLt.removeAll();
	JNIClass[] classes = app.getClasses();
	int mainIndex = 0;
	for (int i = 0; i < classes.length; i++) {
		JNIClass clazz = classes[i];
		if (clazz.equals(app.getMainClass())) mainIndex = i;
		TableItem item = new TableItem(classesLt, SWT.NONE);
		item.setData(clazz);
		item.setText(CLASS_NAME_COLUMN, clazz.getSimpleName());
		item.setText(CLASS_FLAGS_COLUMN, getFlagsString(clazz.getFlags()));
		item.setChecked(clazz.getGenerate());
	}
	TableColumn[] columns = classesLt.getColumns();
	for (int i = 0; i < columns.length; i++) {
		TableColumn column = columns[i];
		column.pack();
	}
	classesLt.setSelection(mainIndex);
}

void updateMembers() {
	membersLt.removeAll();
	membersLt.setHeaderVisible(false);
	TableColumn[] columns = membersLt.getColumns();
	for (int i = 0; i < columns.length; i++) {
		TableColumn column = columns[i];
		column.dispose();
	}
	int[] indices = classesLt.getSelectionIndices();
	if (indices.length != 1) return;
	TableItem classItem = classesLt.getItem(indices[0]);
	JNIClass clazz = (JNIClass)classItem.getData();
	boolean hasNatives = false;
	JNIMethod[] methods = clazz.getDeclaredMethods();
	for (int i = 0; i < methods.length; i++) {
		JNIMethod method = methods[i];
		int mods = method.getModifiers();
		if (hasNatives =((mods & Modifier.NATIVE) != 0)) break;
	}
	membersLt.setRedraw(false);
	if (hasNatives) {
		TableColumn column;
		column = new TableColumn(membersLt, SWT.NONE, METHOD_NAME_COLUMN);
		column.setText("Method");
		column = new TableColumn(membersLt, SWT.NONE, METHOD_FLAGS_COLUMN);
		column.setText("Flags");
		column = new TableColumn(membersLt, SWT.NONE, METHOD_ACCESSOR_COLUMN);
		column.setText("Accessor");
		/*
		column = new TableColumn(membersLt, SWT.NONE, METHOD_EXCLUDE_COLUMN);
		column.setText("Exclude");
		*/
		JNIGenerator.sort(methods);
		for (int i = 0; i < methods.length; i++) {
			JNIMethod method = methods[i];
			if ((method.getModifiers() & Modifier.NATIVE) == 0) continue;
			TableItem item = new TableItem(membersLt, SWT.NONE);
			item.setData(method);
			item.setText(METHOD_NAME_COLUMN, getMethodString(method));
			item.setChecked(method.getGenerate());
			item.setText(METHOD_FLAGS_COLUMN, getFlagsString(method.getFlags()));
			item.setText(METHOD_ACCESSOR_COLUMN, method.getAccessor());
			/*
			item.setText(METHOD_EXCLUDE_COLUMN, methodData.getExclude());
			*/
		}
	} else {
		TableColumn column;
		column = new TableColumn(membersLt, SWT.NONE, FIELD_NAME_COLUMN);
		column.setText("Field");
		column = new TableColumn(membersLt, SWT.NONE, FIELD_FLAGS_COLUMN);
		column.setText("Flags");
		column = new TableColumn(membersLt, SWT.NONE, FIELD_CAST_COLUMN);
		column.setText("Cast");
		column = new TableColumn(membersLt, SWT.NONE, FIELD_ACCESSOR_COLUMN);
		column.setText("Accessor");
		/*
		column = new TableColumn(membersLt, SWT.NONE, FIELD_EXCLUDE_COLUMN);
		column.setText("Exclude");
		*/
		JNIField[] fields = clazz.getDeclaredFields();	
		for (int i = 0; i < fields.length; i++) {
			JNIField field = fields[i];
			int mods = field.getModifiers(); 
			if (((mods & Modifier.PUBLIC) == 0) ||
				((mods & Modifier.FINAL) != 0) ||
				((mods & Modifier.STATIC) != 0)) continue;
			TableItem item = new TableItem(membersLt, SWT.NONE);
			item.setData(field);
			item.setText(FIELD_NAME_COLUMN, getFieldString(field));
			item.setChecked(field.getGenerate());
			item.setText(FIELD_CAST_COLUMN, field.getCast());
			item.setText(FIELD_FLAGS_COLUMN, getFlagsString(field.getFlags()));
			item.setText(FIELD_ACCESSOR_COLUMN, field.getAccessor());
			/*
			item.setText(FIELD_EXCLUDE_COLUMN, fieldData.getExclude());
			*/
		}
	}
	columns = membersLt.getColumns();
	for (int i = 0; i < columns.length; i++) {
		TableColumn column = columns[i];
		column.pack();
	}
	membersLt.setHeaderVisible(true);
	membersLt.setRedraw(true);
}

void updateParameters() {
	paramsLt.removeAll();
	int[] indices = membersLt.getSelectionIndices();
	if (indices.length != 1) {
		paramsLt.setHeaderVisible(false);
		return;
	}
	TableItem memberItem = membersLt.getItem(indices[0]);
	Object data = memberItem.getData();
	if (!(data instanceof JNIMethod)) return;
	paramsLt.setRedraw(false);
	JNIMethod method = (JNIMethod)data;
	JNIParameter[] params = method.getParameters();
	for (int i = 0; i < params.length; i++) {
		JNIParameter param = params[i];
		TableItem item = new TableItem(paramsLt, SWT.NONE);
		item.setData(param);
		item.setText(PARAM_INDEX_COLUMN, String.valueOf(i));
		item.setText(PARAM_TYPE_COLUMN, getClassString(param.getType()));
		item.setText(PARAM_CAST_COLUMN, param.getCast());
		item.setText(PARAM_FLAGS_COLUMN, getFlagsString(param.getFlags()));
	}
	TableColumn[] columns = paramsLt.getColumns();
	for (int i = 0; i < columns.length; i++) {
		TableColumn column = columns[i];
		column.pack();
	}
	paramsLt.setRedraw(true);
	paramsLt.setHeaderVisible(true);
}

void updateGenerate(TableItem item) {
	JNIItem itemData = (JNIItem)item.getData();
	itemData.setGenerate(item.getChecked());
//	MetaData metaData = app.getMetaData();
//	if (itemData instanceof JNIClass) {
//		ClassData data = (ClassData)itemData;
//		metaData.setMetaData(data.getClazz(), data);
//	} else if (itemData instanceof FieldData) {
//		FieldData data = (FieldData)itemData;
//		item.setText(FIELD_FLAGS_COLUMN, getFlagsString(data.getFlags()));
//		metaData.setMetaData(data.getField(), data);
//	} else if (itemData instanceof MethodData) {
//		MethodData data = (MethodData)itemData;
//		item.setText(METHOD_FLAGS_COLUMN, getFlagsString(data.getFlags()));
//		metaData.setMetaData(data.getMethod(), data);
//	} else if (itemData instanceof ParameterData) {
//		ParameterData data = (ParameterData)itemData;
//		item.setText(PARAM_FLAGS_COLUMN, getFlagsString(data.getFlags()));
//		metaData.setMetaData(data.getMethod(), data.getParameter(), data);
//	}
}

boolean updateOutputDir() {
	String outputDirStr = outputDirCb.getText();
	File file = new File(outputDirStr);
	if (!file.exists()) {
		MessageBox dialog = new MessageBox(shell, SWT.OK | SWT.ICON_ERROR);
		dialog.setMessage("Output directory does not exist.");
		dialog.open();
		return false;
	}
	if (!file.isDirectory()) {
		MessageBox dialog = new MessageBox(shell, SWT.OK | SWT.ICON_ERROR);
		dialog.setMessage("Output directory is not a directory.");
		dialog.open();
		return false;
	}
	if (outputDirStr.length() > 0) {
		if (!outputDirStr.equals(app.getOutputDir())) {
			app.setOutputDir(outputDirStr);
		}
		if (outputDirCb.indexOf(outputDirStr) == -1) {
			outputDirCb.add(outputDirStr);
		}
	}
	return true;
}

void updateMainClass() {
	String mainClassStr = mainClassCb.getText();
	if (mainClassStr.length() > 0) {
		if (!mainClassStr.equals(app.getMainClassName())) {
			app.setMainClassName(mainClassStr);
		}
		if (mainClassCb.indexOf(mainClassStr) == -1) {
			mainClassCb.add(mainClassStr);
		}
		if (app.getOutputDir() != null) {
			int index = outputDirCb.indexOf(app.getOutputDir());
			if (index != -1) outputDirCb.select(index);
		}
	}
}

public static void main(String[] args) {
	JNIGeneratorApp gen = new JNIGeneratorApp ();
	if (args.length > 0) {
		gen.setMainClassName(args[0]);
		if (args.length > 1) gen.setOutputDir(args[1]);
	} else {
		gen.setMainClassName(JNIGeneratorApp.getDefaultMainClass());
	}
	JNIGeneratorAppUI ui = new JNIGeneratorAppUI(gen);
	ui.open();
	ui.run();
}

}
