blob: 9f0c8f9e00382526595223e4141411b076f7a348 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2011 Google, Inc.
* 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
*
* Contributors:
* Google, Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.wb.core.controls;
import com.google.common.collect.Lists;
import org.eclipse.jface.viewers.IBaseLabelProvider;
import org.eclipse.jface.viewers.IContentProvider;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.TableViewerColumn;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerFilter;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ControlAdapter;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.events.TypedEvent;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;
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 org.eclipse.swt.widgets.TypedListener;
import org.eclipse.wb.internal.core.model.property.editor.TextControlActionsManager;
import org.eclipse.wb.internal.core.model.property.table.PropertyTable;
import org.eclipse.wb.internal.core.utils.check.Assert;
import java.util.ArrayList;
/**
* Extended ComboBox control for {@link PropertyTable} and combo property editors.
*
* @author sablin_aa
* @coverage core.control
*/
public class CComboBox extends Composite {
private Text m_text;
private Button m_button;
private Canvas m_canvas;
private Shell m_popup;
private TableViewer m_table;
private boolean m_fullDropdownTableWidth = false;
private boolean m_wasFocused;
////////////////////////////////////////////////////////////////////////////
//
// Constructor
//
////////////////////////////////////////////////////////////////////////////
public CComboBox(Composite parent, int style) {
super(parent, style);
createContents(this);
m_wasFocused = isComboFocused();
// add display hook
final Listener displayFocusInHook = new Listener() {
@Override
public void handleEvent(Event event) {
boolean focused = isComboFocused();
if (m_wasFocused && !focused) {
// close DropDown on focus out ComboBox
comboDropDown(false);
}
if (event.widget != CComboBox.this) {
// forward to ComboBox listeners
if (!m_wasFocused && focused) {
event.widget = CComboBox.this;
notifyListeners(SWT.FocusIn, event);
}
if (m_wasFocused && !focused) {
event.widget = CComboBox.this;
notifyListeners(SWT.FocusOut, event);
}
}
m_wasFocused = focused;
}
};
final Listener displayFocusOutHook = new Listener() {
@Override
public void handleEvent(Event event) {
m_wasFocused = isComboFocused();
}
};
{
Display display = getDisplay();
display.addFilter(SWT.FocusIn, displayFocusInHook);
display.addFilter(SWT.FocusOut, displayFocusOutHook);
}
// combo listeners
addControlListener(new ControlAdapter() {
@Override
public void controlResized(ControlEvent e) {
resizeInner();
}
});
addDisposeListener(new DisposeListener() {
@Override
public void widgetDisposed(DisposeEvent e) {
{
// remove Display hooks
Display display = getDisplay();
display.removeFilter(SWT.FocusIn, displayFocusInHook);
display.removeFilter(SWT.FocusOut, displayFocusOutHook);
}
disposeInner();
}
});
}
////////////////////////////////////////////////////////////////////////////
//
// Contents
//
////////////////////////////////////////////////////////////////////////////
protected void createContents(Composite parent) {
createText(parent);
createButton(parent);
createImage(parent);
createPopup(parent);
}
/**
* Create Text widget.
*/
protected void createText(Composite parent) {
m_text = new Text(parent, SWT.NONE);
new TextControlActionsManager(m_text);
// key press processing
m_text.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
switch (e.keyCode) {
case SWT.ESC :
if (isDroppedDown()) {
// close dropdown
comboDropDown(false);
e.doit = false;
} else {
// forward to ComboBox listeners
notifyListeners(SWT.KeyDown, convert2event(e));
}
break;
case SWT.ARROW_UP :
if (isDroppedDown()) {
// prev item in dropdown list
Table table = m_table.getTable();
int index = table.getSelectionIndex() - 1;
table.setSelection(index < 0 ? table.getItemCount() - 1 : index);
e.doit = false;
} else {
// forward to ComboBox listeners
notifyListeners(SWT.KeyDown, convert2event(e));
}
break;
case SWT.ARROW_DOWN :
if (isDroppedDown()) {
// next item in dropdown list
Table table = m_table.getTable();
int index = table.getSelectionIndex() + 1;
table.setSelection(index == table.getItemCount() ? 0 : index);
e.doit = false;
} else if ((e.stateMask & SWT.ALT) != 0) {
// force drop down combo
comboDropDown(true);
e.doit = false;
// return focus to text
setFocus2Text(false);
} else {
// forward to ComboBox listeners
notifyListeners(SWT.KeyDown, convert2event(e));
}
break;
case '\r' :
Table table = m_table.getTable();
if (isDroppedDown() && table.getSelectionIndex() != -1) {
// forward to Table listeners
table.notifyListeners(SWT.Selection, convert2event(e));
} else {
m_text.selectAll();
setSelectionText(getEditText());
// forward to ComboBox listeners
notifyListeners(SWT.Selection, convert2event(e));
}
break;
}
}
});
// modifications processing
m_text.addModifyListener(new ModifyListener() {
@Override
public void modifyText(ModifyEvent e) {
if (isDroppedDown()) {
m_table.refresh();
} else {
// force drop down combo
if (m_text.isFocusControl()) {
comboDropDown(true);
// return focus to text
setFocus2Text(false);
}
}
}
});
}
/**
* Create arrow button.
*/
protected void createButton(Composite parent) {
m_button = new Button(parent, SWT.ARROW | SWT.DOWN);
m_button.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
comboDropDown(!isDroppedDown());
// return focus to text
setFocus2Text(true);
}
});
}
/**
* Create image canvas.
*/
protected void createImage(Composite parent) {
m_canvas = new Canvas(parent, SWT.BORDER);
m_canvas.addPaintListener(new PaintListener() {
@Override
public void paintControl(PaintEvent e) {
Image selectionImage = getSelectionImage();
if (selectionImage != null) {
e.gc.drawImage(selectionImage, 0, 0);
} else {
e.gc.fillRectangle(m_canvas.getClientArea());
}
}
});
}
/**
* Create popup shell with table.
*/
protected void createPopup(Composite parent) {
m_popup = new Shell(getShell(), SWT.BORDER);
m_popup.setLayout(new FillLayout());
createTable(m_popup);
}
/**
* Create table.
*/
protected void createTable(Composite parent) {
m_table = new TableViewer(parent, SWT.FULL_SELECTION);
new TableViewerColumn(m_table, SWT.LEFT);
m_table.getTable().addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
int selectionIndex = m_table.getTable().getSelectionIndex();
setSelectionIndex(selectionIndex);
comboDropDown(false);
// forward to ComboBox listeners
notifyListeners(SWT.Selection, convert2event(e));
}
});
m_table.setContentProvider(getContentProvider());
m_table.setLabelProvider(getLabelProvider());
m_table.addFilter(getFilterProvider());
}
/**
* Placement inner widgets.
*/
protected void resizeInner() {
Rectangle clientArea = getClientArea();
int rightOccupied = 0;
int leftOccupied = 0;
{
// button
m_button.setBounds(
clientArea.width - clientArea.height,
0,
clientArea.height,
clientArea.height);
rightOccupied = clientArea.height;
}
{
Image selectionImage = getSelectionImage();
if (selectionImage != null) {
// image
m_canvas.setSize(clientArea.height, clientArea.height);
leftOccupied = clientArea.height;
} else {
m_canvas.setSize(1, clientArea.height);
leftOccupied = 1;
}
}
{
// text
m_text.setBounds(
leftOccupied,
0,
clientArea.width - rightOccupied - leftOccupied,
clientArea.height);
}
}
/**
* Dispose inner widgets.
*/
protected void disposeInner() {
if (!m_popup.isDisposed()) {
m_popup.dispose();
}
}
////////////////////////////////////////////////////////////////////////////
//
// Providers
//
////////////////////////////////////////////////////////////////////////////
protected IContentProvider getContentProvider() {
return new IStructuredContentProvider() {
@Override
public Object[] getElements(Object inputElement) {
return m_items.toArray(new ComboBoxItem[m_items.size()]);
}
@Override
public void dispose() {
}
@Override
public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
}
};
}
protected IBaseLabelProvider getLabelProvider() {
return new LabelProvider() {
@Override
public Image getImage(Object element) {
ComboBoxItem item = (ComboBoxItem) element;
return item.m_image;
}
@Override
public String getText(Object element) {
ComboBoxItem item = (ComboBoxItem) element;
return item.m_label;
}
};
}
protected ViewerFilter getFilterProvider() {
return new ViewerFilter() {
@Override
public boolean select(Viewer viewer, Object parentElement, Object element) {
String lookingString = m_text.getText().toLowerCase();
if (isDroppedDown() && lookingString.length() > 0) {
ComboBoxItem item = (ComboBoxItem) element;
return item.m_label.toLowerCase().indexOf(lookingString) != -1;
}
return true;
}
};
}
////////////////////////////////////////////////////////////////////////////
//
// Items
//
////////////////////////////////////////////////////////////////////////////
protected static class ComboBoxItem {
public final String m_label;
public final Image m_image;
public ComboBoxItem(String label, Image image) {
m_label = label;
m_image = image;
}
}
ArrayList<ComboBoxItem> m_items = Lists.newArrayList();
/**
* Add new item.
*/
public void addItem(String label, Image image) {
Assert.isTrue(!isDroppedDown());
m_items.add(new ComboBoxItem(label, image));
}
public void addItem(String label) {
addItem(label, null);
}
public void removeAll() {
m_items.clear();
}
public int getItemCount() {
return m_items.size();
}
public String getItemLabel(int index) {
return m_items.get(index).m_label;
}
////////////////////////////////////////////////////////////////////////////
//
// Access
//
////////////////////////////////////////////////////////////////////////////
public boolean isComboFocused() {
return isFocusControl()
|| m_text.isFocusControl()
|| m_button.isFocusControl()
|| m_canvas.isFocusControl()
|| m_popup.isFocusControl()
|| m_table.getTable().isFocusControl();
}
/**
* Edit text.
*/
public String getEditText() {
return m_text.getText();
}
public void setEditText(String text) {
m_text.setText(text == null ? "" : text);
m_text.selectAll();
}
public void setEditSelection(int start, int end) {
m_text.setSelection(start, end);
}
/**
* Read only.
*/
public void setReadOnly(boolean value) {
m_text.setEditable(!value);
m_button.setEnabled(!value);
}
/**
* Drop down width.
*/
public boolean isFullDropdownTableWidth() {
return m_fullDropdownTableWidth;
}
public void setFullDropdownTableWidth(boolean value) {
Assert.isTrue(!isDroppedDown());
m_fullDropdownTableWidth = value;
}
////////////////////////////////////////////////////////////////////////////
//
// Selection
//
////////////////////////////////////////////////////////////////////////////
private int m_selectionIndex = -1;
/**
* Selection index.
*/
public int getSelectionIndex() {
return m_selectionIndex;
}
public void setSelectionIndex(int index) {
m_selectionIndex = index;
if (isDroppedDown()) {
m_table.getTable().setSelection(m_selectionIndex);
}
setEditText(getSelectionText());
}
/**
* Selection text.
*/
private String getSelectionText() {
if (m_selectionIndex != -1 && isDroppedDown()) {
Object itemData = m_table.getTable().getItem(m_selectionIndex).getData();
return ((ComboBoxItem) itemData).m_label;
}
return null;
}
/**
* Selection image.
*/
private Image getSelectionImage() {
return m_selectionIndex != -1 ? m_items.get(m_selectionIndex).m_image : null;
}
public void setSelectionText(String label) {
TableItem[] items = m_table.getTable().getItems();
for (int i = 0; i < items.length; i++) {
TableItem item = items[i];
if (item.getText().equals(label)) {
setSelectionIndex(i);
return;
}
}
// no such item
setSelectionIndex(-1);
setEditText(label);
}
/**
* Adds the listener to receive events.
*/
public void addSelectionListener(SelectionListener listener) {
checkWidget();
if (listener == null) {
SWT.error(SWT.ERROR_NULL_ARGUMENT);
}
TypedListener typedListener = new TypedListener(listener);
addListener(SWT.Selection, typedListener);
addListener(SWT.DefaultSelection, typedListener);
}
////////////////////////////////////////////////////////////////////////////
//
// Popup
//
////////////////////////////////////////////////////////////////////////////
public boolean isDroppedDown() {
return m_popup.isVisible();
}
public void comboDropDown(boolean dropdown) {
// check, may be we already in this drop state
if (dropdown == isDroppedDown()) {
return;
}
// close combo
if (dropdown) {
// initialize
m_table.setInput(m_items);
Table table = m_table.getTable();
TableColumn column = table.getColumn(0);
column.pack();
table.pack();
m_popup.pack();
// compute table size
Rectangle tableBounds = table.getBounds();
tableBounds.height = Math.min(tableBounds.height, table.getItemHeight() * 15);// max 15 items without scrolling
table.setBounds(tableBounds);
// prepare popup point
Point comboLocation = toDisplay(new Point(0, 0));
Point comboSize = getSize();
// compute popup size
Display display = getDisplay();
Rectangle clientArea = display.getClientArea();
int remainingDisplayHeight = clientArea.height - comboLocation.y - comboSize.y - 10;
int preferredHeight = Math.min(tableBounds.height, remainingDisplayHeight);
int remainingDisplayWidth = clientArea.width - comboLocation.x - 10;
int preferredWidth =
isFullDropdownTableWidth()
? Math.min(tableBounds.width, remainingDisplayWidth)
: comboSize.x;
Rectangle popupBounds =
new Rectangle(comboLocation.x,
comboLocation.y + comboSize.y,
preferredWidth,
preferredHeight);
Rectangle trimBounds =
m_popup.computeTrim(popupBounds.x, popupBounds.y, popupBounds.width, popupBounds.height);
m_popup.setBounds(popupBounds.x, popupBounds.y, 2 * popupBounds.width - trimBounds.width, 2
* popupBounds.height
- trimBounds.height);
// adjust column size
column.setWidth(table.getClientArea().width);
// show popup
m_popup.setVisible(true);
table.setSelection(getSelectionIndex());
} else {
// hide popup
m_popup.setVisible(false);
}
}
protected final void setFocus2Text(final boolean selectAll) {
getDisplay().asyncExec(new Runnable() {
final boolean m_selectAll = selectAll;
@Override
public void run() {
if (!m_text.isDisposed()) {
m_text.setFocus();
if (m_selectAll) {
m_text.selectAll();
}
}
}
});
}
////////////////////////////////////////////////////////////////////////////
//
// Utilities
//
////////////////////////////////////////////////////////////////////////////
protected static Event convert2event(TypedEvent tEvent) {
Event event = new Event();
event.widget = tEvent.widget;
event.display = tEvent.display;
event.widget = tEvent.widget;
event.time = tEvent.time;
event.data = tEvent.data;
if (tEvent instanceof KeyEvent) {
KeyEvent kEvent = (KeyEvent) tEvent;
event.character = kEvent.character;
event.keyCode = kEvent.keyCode;
event.stateMask = kEvent.stateMask;
event.doit = kEvent.doit;
}
if (tEvent instanceof SelectionEvent) {
SelectionEvent sEvent = (SelectionEvent) tEvent;
event.item = sEvent.item;
event.x = sEvent.x;
event.y = sEvent.y;
event.width = sEvent.width;
event.height = sEvent.height;
event.detail = sEvent.detail;
event.stateMask = sEvent.stateMask;
event.text = sEvent.text;
event.doit = sEvent.doit;
}
return event;
}
}