package bndtools.editor.common;

import org.eclipse.jface.viewers.CellEditor;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.TraverseEvent;
import org.eclipse.swt.events.TraverseListener;
import org.eclipse.swt.widgets.Table;

/**
 * Well ... the JFace <CODE>TableViewer<CODE> class does not provide
 * text celleditor keyboard support like if TAB is pressed in a column,
 * the focus does not go into the next column etc.,
 *
 * After observing JDT method signature refactoring widget which does the
 * desired keyboard support, a generic utility listener has been developed
 * on the same lines.
 *
 * Usage goes as ...
 *
 * <P><PRE><CODE>
 *
 * 		TableViewer tblViewer = new TableViewer(SWT.SINGLE | SWT.FULL_SELECTION | SWT.| SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER)
 * 		...
 * 		CellEditor[] cellEditors = ...
 * 		...
 * 		tblViewer.setCellEditors(cellEditors);
 *
 * 		TableViewerKeyBoardSupporter supporter = new TableViewerKeyBoardSupporter(tblViewer);
 * 		supporter.startSupport();
 *
 * </CODE>
 * </PRE>
 * </P>
 */
public class TableViewerKeyBoardSupporter {
	protected TableViewer fTableViewer = null;

	public TableViewerKeyBoardSupporter(TableViewer tableViewer) {
		fTableViewer = tableViewer;
	}

	/**
	 * After the cell editors have been set on tableviewer, this method should
	 * be called to start giving keyboard support.
	 */
	public void startSupport() {
		/* add table key listener */
		getTable().addKeyListener(new TableKeyListener(this));

		/* add table traverse listener */
		getTable().addTraverseListener(new TableTraverseListener(this));

		/* add table-textcelleditors key and traverse listeners */
		CellEditor[] cellEditors = fTableViewer.getCellEditors();
		if (cellEditors != null) {
			for (int colIndex = 0; colIndex < cellEditors.length; colIndex++) {
				CellEditor cellEditor = cellEditors[colIndex];
				if (cellEditor != null) {
					// cellEditor.getControl().addKeyListener(new
					// CellEditorKeyListener(this, cellEditor, colIndex));
					cellEditor.getControl()
						.addTraverseListener(new CellEditorTraverseListener(this, cellEditor, colIndex));
				}
			}
		}
	}

	protected TableViewer getTableViewer() {
		return fTableViewer;
	}

	protected Table getTable() {
		return fTableViewer.getTable();
	}

	protected int nextColumn(int column) {
		return (column >= getTable().getColumnCount() - 1) ? 0 : column + 1;
	}

	protected int prevColumn(int column) {
		return (column <= 0) ? getTable().getColumnCount() - 1 : column - 1;
	}

	protected void editColumnOrNextPossible(final int column) {
		Object selectedElem = getSelectedElement();
		if (selectedElem == null)
			return;

		int nextColumn = column;
		do {
			fTableViewer.editElement(selectedElem, nextColumn);
			if (fTableViewer.isCellEditorActive())
				return;
			nextColumn = nextColumn(nextColumn);
		} while (nextColumn != column);
	}

	protected void editColumnOrPrevPossible(int column) {
		Object selectedElem = getSelectedElement();
		if (selectedElem == null)
			return;

		int prevColumn = column;
		do {
			fTableViewer.editElement(selectedElem, prevColumn);
			if (fTableViewer.isCellEditorActive())
				return;
			prevColumn = prevColumn(prevColumn);
		} while (prevColumn != column);
	}

	private Object getSelectedElement() {
		IStructuredSelection selection = (IStructuredSelection) fTableViewer.getSelection();
		if (selection == null || selection.isEmpty())
			return null;

		return selection.getFirstElement();
	}
}

class CellEditorKeyListener extends KeyAdapter {
	private TableViewerKeyBoardSupporter	fKeyBoardSupporter	= null;
	private CellEditor						fEditor				= null;
	private int								fEditorColumn		= -1;

	public CellEditorKeyListener(TableViewerKeyBoardSupporter keyBoardSupporter, CellEditor editor, int editorColumn) {
		fKeyBoardSupporter = keyBoardSupporter;
		fEditor = editor;
		fEditorColumn = editorColumn;
	}

	@Override
	public void keyPressed(KeyEvent e) {
		if (e.stateMask == SWT.MOD1 || e.stateMask == SWT.MOD2) {
			if (e.keyCode == SWT.ARROW_UP || e.keyCode == SWT.ARROW_DOWN) {
				// allow starting multi-selection even if in edit mode
				fEditor.deactivate();
				e.doit = false;
				return;
			}
		}

		if (e.stateMask != SWT.NONE)
			return;

		switch (e.keyCode) {
			case SWT.ARROW_DOWN :
				e.doit = false;
				int nextRow = fKeyBoardSupporter.getTable()
					.getSelectionIndex() + 1;
				if (nextRow >= fKeyBoardSupporter.getTable()
					.getItemCount())
					break;
				fKeyBoardSupporter.getTable()
					.setSelection(nextRow);
				fKeyBoardSupporter.editColumnOrPrevPossible(fEditorColumn);
				break;

			case SWT.ARROW_UP :
				e.doit = false;
				int prevRow = fKeyBoardSupporter.getTable()
					.getSelectionIndex() - 1;
				if (prevRow < 0)
					break;
				fKeyBoardSupporter.getTable()
					.setSelection(prevRow);
				fKeyBoardSupporter.editColumnOrPrevPossible(fEditorColumn);
				break;

			case SWT.F2 :
				e.doit = false;
				fEditor.deactivate();
				break;

			default :
				break;
		}
	}
}

class CellEditorTraverseListener implements TraverseListener {
	private TableViewerKeyBoardSupporter	fKeyBoardSupporter	= null;
	private CellEditor						fEditor				= null;
	private int								fEditorColumn		= -1;

	public CellEditorTraverseListener(TableViewerKeyBoardSupporter keyBoardSupporter, CellEditor editor,
		int editorColumn) {
		fKeyBoardSupporter = keyBoardSupporter;
		fEditor = editor;
		fEditorColumn = editorColumn;
	}

	/*
	 * (non-Javadoc)
	 * @see
	 * org.eclipse.swt.events.TraverseListener#keyTraversed(org.eclipse.swt.
	 * events.TraverseEvent)
	 */
	@Override
	public void keyTraversed(TraverseEvent e) {

		switch (e.detail) {
			case SWT.TRAVERSE_TAB_NEXT :
				fKeyBoardSupporter.editColumnOrNextPossible(fKeyBoardSupporter.nextColumn(fEditorColumn));
				e.detail = SWT.TRAVERSE_NONE;
				break;

			case SWT.TRAVERSE_TAB_PREVIOUS :
				fKeyBoardSupporter.editColumnOrPrevPossible(fKeyBoardSupporter.prevColumn(fEditorColumn));
				e.detail = SWT.TRAVERSE_NONE;
				break;

			case SWT.TRAVERSE_ESCAPE :
				fKeyBoardSupporter.getTableViewer()
					.cancelEditing();
				e.detail = SWT.TRAVERSE_NONE;
				break;

			case SWT.TRAVERSE_RETURN :
				fEditor.deactivate();
				e.detail = SWT.TRAVERSE_NONE;
				break;

			default :
				break;
		}
	}
}

class TableTraverseListener implements TraverseListener {
	private TableViewerKeyBoardSupporter fKeyBoardSupporter = null;

	public TableTraverseListener(TableViewerKeyBoardSupporter keyBoardSupporter) {
		fKeyBoardSupporter = keyBoardSupporter;
	}

	/*
	 * (non-Javadoc)
	 * @see
	 * org.eclipse.swt.events.TraverseListener#keyTraversed(org.eclipse.swt.
	 * events.TraverseEvent)
	 */
	@Override
	public void keyTraversed(TraverseEvent e) {
		if (e.detail == SWT.TRAVERSE_RETURN && e.stateMask == SWT.NONE) {
			fKeyBoardSupporter.editColumnOrNextPossible(0);
			e.detail = SWT.TRAVERSE_NONE;
		}
	}
}

class TableKeyListener extends KeyAdapter {
	private TableViewerKeyBoardSupporter fKeyBoardSupporter = null;

	public TableKeyListener(TableViewerKeyBoardSupporter keyBoardSupporter) {
		fKeyBoardSupporter = keyBoardSupporter;
	}

	/*
	 * (non-Javadoc)
	 * @see
	 * org.eclipse.swt.events.KeyListener#keyPressed(org.eclipse.swt.events.
	 * KeyEvent)
	 */
	@Override
	public void keyPressed(KeyEvent e) {
		if (e.keyCode == SWT.F2 && e.stateMask == SWT.NONE) {
			fKeyBoardSupporter.editColumnOrNextPossible(0);
			e.doit = false;
		}
	}
}
