/**
* Copyright (c) 2009, 2010 Mark Feber, MulgaSoft
*
* 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 com.mulgasoft.emacsplus.execute;
import java.util.SortedMap;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.PopupDialog;
import org.eclipse.jface.layout.TableColumnLayout;
import org.eclipse.jface.viewers.ColumnWeightData;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseTrackListener;
import org.eclipse.swt.graphics.Color;
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.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Label;
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.ui.contexts.IContextService;
import org.eclipse.ui.texteditor.ITextEditor;
// with help from org.eclipse.ui.internal.keys.KeyAssistDialog;
/**
* @author Mark Feber - initial API and implementation
*/
public class SelectionDialog extends PopupDialog {
// TODO: there should be a way to determine this value
private static int SIZE_ADJUST = 15;
private static int MIN_COLUMNS = 3;
private Point sizeHint;
private boolean clicked = false;
private boolean mouseIn = false;
protected ITextEditor editor;
protected ISelectExecute minibuffer;
private SortedMap<String,?> selectables = null;
protected Shell tip = null;
protected void setSelectables(SortedMap<String,?> selectables) {
this.selectables = selectables;
}
protected SortedMap<String,?> getSelectables() {
return selectables;
}
protected boolean hasSelectables() {
return selectables != null && selectables.size() > 0;
}
protected void clearSelectables() {
selectables = null;
}
protected String[] getSelectableKeys() {
return ((selectables != null) ? selectables.keySet().toArray(new String[0]) : null);
}
// super(parent, shellStyle, takeFocusOnOpen, persistSize, persistLocation, showDialogMenu, showPersistActions, titleText, infoText);
@SuppressWarnings("deprecation") // backward compatibility
public SelectionDialog(Shell parent, ISelectExecute mini, ITextEditor editor) {
// Europa compatible constructor
super((Shell) null, PopupDialog.HOVER_SHELLSTYLE, false, false, false, false, null, null);
this.editor = editor;
this.minibuffer = mini;
}
public void shutdown() {
this.editor = null;
this.minibuffer = null;
clearSelectables();
close();
}
// TODO rework..as super.open seems to repeat the create!
public final int open(SortedMap<String, ?> selectables) {
setSelectables(selectables);
Shell shell = getShell();
if (shell != null) {
close();
}
sizeHint = configureSize();
create();
// Configure the size and location.
configureLocation(sizeHint);
// and open the dialog
return super.open();
}
@Override
public boolean close() {
// make sure we're clean
disposeTip();
return super.close();
}
public boolean mouseIn() {
return mouseIn;
}
private void disposeTip() {
if (tip != null && !tip.isDisposed()) {
tip.dispose();
tip = null;
}
}
/**
* behavior on mouse click
*/
protected void execute(String key) {
minibuffer.execute(key);
}
protected int getSizeAdjustment() {
return SIZE_ADJUST;
}
/**
* Sets the size for the dialog.
*
* The width is slightly less the workbench window's width.
* The dialog's height is the packed height of the dialog up to a
* maximum of half the height of the workbench window.
*
* @return The size of the dialog
*/
private final Point configureSize() {
int maxW = 0;
int maxH = 0;
// Enforce maximum sizing.
Shell workbenchWindowShell = editor.getEditorSite().getShell();
if (workbenchWindowShell != null) {
Point workbenchWindowSize = workbenchWindowShell.getSize();
// TODO: just a guess for now. I'd like to determine the current borders and subtract them
// int maxW = workbenchWindowSize.x -25;
maxW = workbenchWindowShell.getClientArea().width - getSizeAdjustment();
maxH = workbenchWindowSize.y / 2;
}
return new Point(maxW, maxH);
}
/**
* Sets the position for the dialog based on the position of the workbench
* window. The dialog is flush with the bottom right corner of the workbench
* window. However, the dialog will not appear outside of the display's
* client area.
*
* @param size
* The final size of the dialog; must not be <code>null</code>.
*/
private final void configureLocation(final Point size) {
final Shell shell = getShell();
final Shell workbenchWindowShell = editor.getEditorSite().getShell();
final int xCoord;
final int yCoord;
if (workbenchWindowShell != null) {
/*
* Position the shell at the bottom right corner of the workbench
* window
*/
// TODO: The constants are just guesses
final Rectangle workbenchWindowBounds = workbenchWindowShell
.getBounds();
xCoord = workbenchWindowBounds.x + workbenchWindowBounds.width - size.x - 10;
yCoord = workbenchWindowBounds.y + workbenchWindowBounds.height - size.y - 35;
} else {
xCoord = 0;
yCoord = 0;
}
final Rectangle bounds = new Rectangle(xCoord, yCoord, size.x, size.y);
shell.setBounds(getConstrainedShellBounds(bounds));
}
/**
* Creates the content area for the key assistant. This creates a table and
* places it inside the composite. The composite will contain a list of all
* the key bindings.
*
* @param parent
* The parent composite to contain the dialog area; must not be
* <code>null</code>.
*/
protected final Control createDialogArea(final Composite parent) {
// First, register the shell type with the context support
registerShellType();
// Create a composite for the dialog area.
final Composite composite = new Composite(parent, SWT.NONE);
composite.setBackground(parent.getBackground());
createTableDialogArea(composite);
return composite;
}
private final void createTableDialogArea(final Composite parent) {
String[] inputKeys = getSelectableKeys();
int columnCount = 0;
Point dimens = getColumnCount( parent, inputKeys, sizeHint.x);
int count = dimens.x;
GridLayout compositeLayout = new GridLayout(count,true);
parent.setLayout(compositeLayout);
parent.setLayoutData(new GridData(GridData.FILL_BOTH));
Table table = new Table(parent, SWT.V_SCROLL | SWT.HORIZONTAL | SWT.WRAP | SWT.FULL_SELECTION); //| SWT.MULTI);
GridData gridData = new GridData(GridData.FILL_BOTH);
table.setLayoutData(gridData);
table.setBackground(parent.getBackground());
table.setLinesVisible(true);
table.setHeaderVisible(false);
int columnWidth = (sizeHint.x - getSizeAdjustment()) / count;
TableColumn[] columns = new TableColumn[count];
for (int i = 0; i < count; i++) {
columns[i] = new TableColumn(table, SWT.LEFT, columnCount++);
columns[i].setWidth(columnWidth);
}
TableColumnLayout layout = new TableColumnLayout();
for (int i = 0; i < count; i++) {
layout.setColumnData(columns[i], new ColumnWeightData(100/count,columnWidth,false));
}
parent.setLayout(layout);
int len = inputKeys.length;
int rowCount = len / columnCount;
if ((len - rowCount * columnCount) > 0) {
rowCount++;
}
for (int i = 0; i < rowCount; i++) {
String[] row = new String[columnCount];
for (int j = 0; j < columnCount; j++) {
int sourceIndex = i * columnCount + j;
row[j] = (sourceIndex < len ? (String) inputKeys[sourceIndex] : ""); //$NON-NLS-1$
}
TableItem item = new TableItem(table, SWT.NULL);
item.setText(row);
}
table.pack();
sizeHint.y = Math.min(table.getBounds().height + getSizeAdjustment(),sizeHint.y);
Dialog.applyDialogFont(parent);
addTableListeners(table);
}
/**
* Determine the appropriate number of columns. If the number of items is less than
* the computed number, choose the smaller.
*
* @param parent
* @param vals
* @param width
* @return
*/
private Point getColumnCount(Composite parent, Object[] vals, int width) {
Point result = new Point(0,0);
int iWidth = MIN_COLUMNS;
int len = vals.length;
int size = 0;
Label l = null;
try {
l = new Label(parent, SWT.NULL);
for (int i = 0; i < len; i++) {
l.setText((String) vals[i]);
size = Math.max(size, l.computeSize(SWT.DEFAULT, SWT.DEFAULT).x);
}
result = l.getSize();
l.dispose();
result.x = Math.max(iWidth, width / (size + 5));
result.x = Math.min(result.x, vals.length);
} catch (Exception e) {
result = new Point(0,0);
}
return result;
}
// Listeners
class ItemPkg {
int index = 0;
TableItem item = null;
ItemPkg(TableItem item, int index){
this.item = item;
this.index = index;
}
String getText() {
return item.getText(index);
}
Rectangle getBounds() {
return item.getBounds(index);
}
}
private ItemPkg getCell(Event event, Table table) {
ItemPkg result = null;
TableItem item = table.getItem(new Point(event.x,event.y));
if (item != null) {
for (int i = 0; i < table.getColumnCount(); i++) {
if (item.getBounds(i).contains(event.x, event.y)) {
result = new ItemPkg(item,i);
break;
}
}
}
return result;
}
private String getCellText(Event event, Table table) {
String result = null;
ItemPkg item = getCell(event,table);
if (item != null){
result = item.getText();
}
return result;
}
private ItemPkg getCell(MouseEvent event, Table table) {
ItemPkg result = null;
TableItem item = table.getItem(new Point(event.x,event.y));
if (item != null) {
for (int i = 0; i < table.getColumnCount(); i++) {
if (item.getBounds(i).contains(event.x, event.y)) {
result = new ItemPkg(item,i);
break;
}
}
}
return result;
}
protected boolean hasToolTips() {
return false;
}
void showTip(String txt, ItemPkg tp, Table table) {}
private void addTableListeners(final Table table) {
final Color fc = table.getForeground();
final Color bc = table.getBackground();
if (hasToolTips()) {
addToolTipListener(table);
}
// We need to listen to enter/exit so we know on
// minibuffer focusLost whether the focus was
// lost to the selection dialog, or some other part
table.addListener(SWT.MouseEnter, new Listener() {
public void handleEvent(Event event) {
mouseIn = true;
}
});
table.addListener(SWT.MouseExit, new Listener() {
public void handleEvent(Event event) {
mouseIn = false;
}
});
table.addListener(SWT.MouseDown, new Listener() {
public void handleEvent(Event event) {
clicked = true;
}
});
table.addListener(SWT.MouseDoubleClick, new Listener() {
public void handleEvent(Event event) {
String t = getCellText(event, table);
if (t != null) {
disposeTip();
execute(t);
}
}
});
// TODO: Can we do better?
// Disable lame eclipse selection behavior
table.addListener(SWT.EraseItem, new Listener() {
public void handleEvent(Event event) {
if ((event.detail & SWT.SELECTED) != 0) {
// Color ofc = event.gc.getForeground();
Color obc = event.gc.getBackground();
event.gc.setForeground(fc);
event.gc.setBackground(bc);
TableItem item = (TableItem) event.item;
// Rectangle r = item.getBounds(event.index);
event.gc.setBackground(item.getBackground(event.index));
event.gc.setForeground(item.getForeground(event.index));
// event.gc.drawText(" ", r.x, r.y, false);
// event.gc.drawText(item.getText(), r.x, r.y, false);
// event.gc.drawString(event.text, event.x, event.y);
event.detail &= ~SWT.SELECTED;
event.detail &= ~SWT.BACKGROUND;
event.detail &= ~SWT.FOCUSED;
event.detail &= ~SWT.HOT;
// event.doit = false;
// event.gc.setForeground(ofc);
event.gc.setBackground(obc);
}
}
});
}
/**
* If sub class support tool tips, than add generic tip handling
*
* @param table
*/
private void addToolTipListener(final Table table) {
table.addMouseTrackListener(new MouseTrackListener() {
public void mouseEnter(MouseEvent e) {
disposeTip();
}
public void mouseExit(MouseEvent e) {
disposeTip();
if (clicked) {
close();
}
}
public void mouseHover(MouseEvent e) {
ItemPkg tp = getCell(e, table);
if (tp != null) {
String t = tp.getText();
disposeTip();
if (t != null) {
showTip(t,tp,table);
}
}
}
});
}
// TODO: What does it all mean Mr. Natural?
/**
* Registers the shell as the same type as its parent with the context
* support. This ensures that it does not modify the current state of the
* application.
*/
private final void registerShellType() {
final Shell shell = getShell();
final IContextService contextService = (IContextService) editor.getEditorSite().getWorkbenchWindow().getWorkbench().getService(IContextService.class);
contextService.registerShell(shell, contextService.getShellType((Shell) shell.getParent()));
}
}