/******************************************************************************* * 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 org.eclipse.swt.SWT; import org.eclipse.swt.custom.CCombo; import org.eclipse.swt.events.DisposeEvent; import org.eclipse.swt.events.DisposeListener; import org.eclipse.swt.events.SelectionListener; import org.eclipse.swt.graphics.Color; 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.Combo; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; 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.TypedListener; import java.util.Locale; /** * {@link Control} like {@link Combo} or {@link CCombo} that shows {@link Table} with image/text as * drop-down. * * @author mitin_aa * @author scheglov_ke * @coverage core.control */ public class CTableCombo extends Composite { protected Button m_arrow; protected CImageLabel m_text; protected Shell m_popup; protected Table m_table; protected boolean hasFocus; // public CTableCombo(Composite parent, int style) { super(parent, style = checkStyle(style)); init(parent, style); } static int checkStyle(int style) { int mask = SWT.BORDER | SWT.READ_ONLY | SWT.FLAT; return style & mask; } private void init(Composite parent, int style) { m_arrow = new Button(this, SWT.ARROW | SWT.DOWN | SWT.NO_FOCUS); m_text = new CImageLabel(this, style & ~SWT.BORDER); m_text.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_LIST_BACKGROUND)); final Shell shell = getShell(); m_popup = new Shell(shell, SWT.NONE); m_table = new Table(m_popup, SWT.FULL_SELECTION); new TableColumn(m_table, SWT.NONE); Listener listener = new Listener() { public void handleEvent(Event event) { if (m_popup == event.widget) { handlePopupEvent(event); return; } if (m_text == event.widget) { handleTextEvent(event); return; } if (m_table == event.widget) { handleTableEvent(event); return; } if (m_arrow == event.widget) { handleArrowEvent(event); return; } if (CTableCombo.this == event.widget) { handleComboEvent(event); return; } } }; final Listener shellListener = new Listener() { public void handleEvent(Event event) { switch (event.type) { case SWT.Dispose : case SWT.Move : case SWT.Resize : if (!isDisposed()) { dropDown(false); } break; } } }; final int[] comboEvents = {SWT.Dispose, SWT.Move, SWT.Resize}; for (int i = 0; i < comboEvents.length; i++) { addListener(comboEvents[i], listener); // HACK: hide popup when parent changed shell.addListener(comboEvents[i], shellListener); } addDisposeListener(new DisposeListener() { public void widgetDisposed(DisposeEvent e) { for (int i = 0; i < comboEvents.length; i++) { shell.removeListener(comboEvents[i], shellListener); } } }); int[] popupEvents = {SWT.Close, SWT.Paint, SWT.Deactivate}; for (int i = 0; i < popupEvents.length; i++) { m_popup.addListener(popupEvents[i], listener); } int[] textEvents = { SWT.KeyDown, SWT.KeyUp, SWT.Modify, SWT.MouseDown, SWT.MouseUp, SWT.MouseDoubleClick, SWT.Traverse, SWT.FocusIn, SWT.FocusOut}; for (int i = 0; i < textEvents.length; i++) { m_text.addListener(textEvents[i], listener); } int[] tableEvents = { SWT.MouseUp, SWT.Selection, SWT.Traverse, SWT.KeyDown, SWT.KeyUp, SWT.FocusIn, SWT.FocusOut}; for (int i = 0; i < tableEvents.length; i++) { m_table.addListener(tableEvents[i], listener); } int[] arrowEvents = {SWT.Selection, SWT.FocusIn, SWT.FocusOut}; for (int i = 0; i < arrowEvents.length; i++) { m_arrow.addListener(arrowEvents[i], listener); } } protected void handleTableEvent(Event event) { switch (event.type) { case SWT.FocusIn : { if (hasFocus) { return; } hasFocus = true; Event e = new Event(); e.time = event.time; notifyListeners(SWT.FocusIn, e); break; } case SWT.FocusOut : { final int time = event.time; event.display.asyncExec(new Runnable() { public void run() { if (CTableCombo.this.isDisposed()) { return; } Control focusControl = getDisplay().getFocusControl(); if (focusControl == m_text || focusControl == m_arrow) { return; } hasFocus = false; Event e = new Event(); e.time = time; notifyListeners(SWT.FocusOut, e); } }); break; } case SWT.MouseUp : { if (event.button != 1) { return; } dropDown(false); Event e = new Event(); e.time = event.time; notifyListeners(SWT.DefaultSelection, e); break; } case SWT.Selection : { int index = m_table.getSelectionIndex(); if (index == -1) { return; } TableItem item = m_table.getItem(index); m_text.setText(item.getText()); m_text.setImage(item.getImage()); //m_text.selectAll(); m_table.setSelection(index); Event e = new Event(); e.time = event.time; e.stateMask = event.stateMask; e.doit = event.doit; notifyListeners(SWT.Selection, e); event.doit = e.doit; dropDown(false); break; } case SWT.Traverse : { switch (event.detail) { case SWT.TRAVERSE_TAB_NEXT : case SWT.TRAVERSE_RETURN : case SWT.TRAVERSE_ESCAPE : case SWT.TRAVERSE_ARROW_PREVIOUS : case SWT.TRAVERSE_ARROW_NEXT : event.doit = false; break; } Event e = new Event(); e.time = event.time; e.detail = event.detail; e.doit = event.doit; e.keyCode = event.keyCode; notifyListeners(SWT.Traverse, e); event.doit = e.doit; break; } case SWT.KeyUp : { Event e = new Event(); e.time = event.time; e.character = event.character; e.keyCode = event.keyCode; e.stateMask = event.stateMask; notifyListeners(SWT.KeyUp, e); break; } case SWT.KeyDown : { if (event.character == SWT.ESC) { // escape key cancels popups dropDown(false); } if (event.character == SWT.CR || event.character == '\t') { // Enter and Tab cause default selection dropDown(false); Event e = new Event(); e.time = event.time; e.stateMask = event.stateMask; notifyListeners(SWT.DefaultSelection, e); } // At this point the widget may have been disposed. // If so, do not continue. if (isDisposed()) { break; } Event e = new Event(); e.time = event.time; e.character = event.character; e.keyCode = event.keyCode; e.stateMask = event.stateMask; notifyListeners(SWT.KeyDown, e); break; } } } protected void handlePopupEvent(Event event) { switch (event.type) { case SWT.Paint : // draw black rectangle around list Rectangle listRect = m_table.getBounds(); Color black = getDisplay().getSystemColor(SWT.COLOR_BLACK); event.gc.setForeground(black); event.gc.drawRectangle(0, 0, listRect.width + 1, listRect.height + 1); break; case SWT.Close : event.doit = false; dropDown(false); break; } } protected void handleComboEvent(Event event) { switch (event.type) { case SWT.Dispose : if (m_popup != null && !m_popup.isDisposed()) { m_popup.dispose(); } m_popup = null; m_text = null; m_arrow = null; break; case SWT.Move : dropDown(false); break; case SWT.Resize : internalLayout(); break; } } protected void handleArrowEvent(Event event) { switch (event.type) { case SWT.FocusIn : { if (hasFocus) { return; } hasFocus = true; Event e = new Event(); e.time = event.time; notifyListeners(SWT.FocusIn, e); break; } case SWT.Selection : { boolean wasDropped = isDropped(); dropDown(!wasDropped); if (wasDropped) { m_text.forceFocus(); } break; } } } protected void handleTextEvent(Event event) { switch (event.type) { case SWT.FocusIn : { if (hasFocus) { return; } hasFocus = true; //if (getEditable()) Event e = new Event(); e.time = event.time; notifyListeners(SWT.FocusIn, e); break; } case SWT.FocusOut : { final int time = event.time; event.display.asyncExec(new Runnable() { public void run() { if (CTableCombo.this.isDisposed()) { return; } Control focusControl = getDisplay().getFocusControl(); if (focusControl == m_table || focusControl == m_arrow || focusControl != null && focusControl.getParent() == CTableCombo.this) { return; } hasFocus = false; Event e = new Event(); e.time = time; notifyListeners(SWT.FocusOut, e); } }); break; } case SWT.KeyDown : { if (event.character == SWT.ESC) { // escape key cancels popup dropDown(false); } if (event.character == SWT.CR) { dropDown(false); Event e = new Event(); e.time = event.time; e.stateMask = event.stateMask; notifyListeners(SWT.DefaultSelection, e); } // At this point the widget may have been disposed. // If so, do not continue. if (isDisposed()) { break; } if (event.character == '+') { dropDown(true); } if (isDropped()) { if (event.keyCode == SWT.ARROW_UP || event.keyCode == SWT.ARROW_DOWN) { int oldIndex = getSelectionIndex(); if (event.keyCode == SWT.ARROW_UP) { select(Math.max(oldIndex - 1, 0)); } else { select(Math.min(oldIndex + 1, getItemCount() - 1)); } if (oldIndex != getSelectionIndex()) { Event e = new Event(); e.time = event.time; e.stateMask = event.stateMask; notifyListeners(SWT.Selection, e); } // At this point the widget may have been disposed. // If so, do not continue. if (isDisposed()) { break; } } } if (Character.isLetter(event.character)) { int oldIndex = getSelectionIndex(); int index = -1; for (int i = 0; i < getItemCount(); i++) { String item = getItem(i).toUpperCase(Locale.ENGLISH); if (item.length() != 0 && item.charAt(0) == Character.toUpperCase(event.character)) { index = i; break; } } if (index != -1) { select(Math.max(index, 0)); if (oldIndex != getSelectionIndex()) { Event e = new Event(); e.time = event.time; e.stateMask = event.stateMask; notifyListeners(SWT.Selection, e); } } } Event e = new Event(); e.time = event.time; e.character = event.character; e.keyCode = event.keyCode; e.stateMask = event.stateMask; if (m_text != null && !m_text.isDisposed()) { notifyListeners(SWT.KeyDown, e); } break; } case SWT.KeyUp : { Event e = new Event(); e.time = event.time; e.character = event.character; e.keyCode = event.keyCode; e.stateMask = event.stateMask; notifyListeners(SWT.KeyUp, e); break; } case SWT.Modify : { m_table.deselectAll(); Event e = new Event(); e.time = event.time; notifyListeners(SWT.Modify, e); break; } case SWT.MouseDown : { if (event.button != 1) { return; } m_text.forceFocus(); boolean dropped = isDropped(); dropDown(!dropped); if (!dropped) { m_text.forceFocus(); } break; } case SWT.MouseDoubleClick : { notifyListeners(SWT.MouseDoubleClick, event); break; } case SWT.Traverse : { switch (event.detail) { case SWT.TRAVERSE_RETURN : case SWT.TRAVERSE_ARROW_PREVIOUS : case SWT.TRAVERSE_ARROW_NEXT : // The enter causes default selection and // the arrow keys are used to manipulate the list contents so // do not use them for traversal. event.doit = false; break; case SWT.TRAVERSE_TAB_NEXT : case SWT.TRAVERSE_TAB_PREVIOUS : event.doit = true; break; } Event e = new Event(); e.time = event.time; e.detail = event.detail; e.doit = event.doit; e.keyCode = event.keyCode; notifyListeners(SWT.Traverse, e); event.doit = e.doit; break; } } } private void dropDown(boolean drop) { if (drop == isDropped()) { return; } if (!drop) { m_popup.setVisible(false); m_text.setFocus(); return; } int index = m_table.getSelectionIndex(); if (index != -1) { m_table.setTopIndex(index); m_table.setSelection(index); } m_table.pack(); Point point = getParent().toDisplay(getLocation()); Point comboSize = getSize(); //Rectangle tableRect = m_table.getBounds(); //int width = Math.max(comboSize.x, tableRect.width + 2); int width = comboSize.x - 1; // only one column m_table.getColumn(0).setWidth(width - 5); if (!(m_popup.getLayout() instanceof FillLayout)) { m_popup.setLayout(new FillLayout()); } int itemCount = m_table.getItemCount(); if (itemCount > 20) { itemCount = 20; } int height = Math.min( m_table.getItemHeight() * itemCount + 5, Display.getCurrent().getClientArea().height - point.y - 20); m_popup.setBounds(point.x, point.y + comboSize.y, width, height); m_popup.layout(); m_popup.setVisible(true); m_text.setFocus(); if (index != -1) { m_table.setTopIndex(index); m_table.setSelection(index); } } @Override public Point computeSize(int wHint, int hHint, boolean changed) { checkWidget(); int width = 0, height = 0; Point textSize = m_text.computeSize(wHint, SWT.DEFAULT, changed); Point arrowSize = m_arrow.computeSize(SWT.DEFAULT, SWT.DEFAULT, changed); int tableWidth; { TableColumn column = m_table.getColumn(0); column.pack(); tableWidth = column.getWidth(); } // int borderWidth = getBorderWidth(); height = Math.max(hHint, Math.max(textSize.y, arrowSize.y) + 2 * borderWidth); width = Math.max(wHint, Math.max(textSize.x + arrowSize.x, tableWidth) + 2 * borderWidth); // return new Point(width, height); } private void internalLayout() { if (isDropped()) { dropDown(false); } Rectangle rect = getClientArea(); int width = rect.width; int height = rect.height; Point arrowSize = m_arrow.computeSize(SWT.DEFAULT, height); m_text.setBounds(rect.x, rect.y, width - arrowSize.x, height); m_arrow.setBounds(rect.x + width - arrowSize.x, rect.y, arrowSize.x, arrowSize.y); } private boolean isDropped() { return m_popup.isVisible(); } @Override public boolean isFocusControl() { checkWidget(); if (m_text.isFocusControl() || m_arrow.isFocusControl() || m_table.isFocusControl() || m_popup.isFocusControl()) { return true; } return super.isFocusControl(); } public void select(int index) { checkWidget(); if (index == -1) { m_table.deselectAll(); m_text.setText(""); //$NON-NLS-1$ m_text.setImage(null); return; } if (0 <= index && index < m_table.getItemCount()) { if (index != getSelectionIndex()) { TableItem item = m_table.getItem(index); m_text.setText(item.getText()); m_text.setImage(item.getImage()); m_table.select(index); m_table.showSelection(); } } } @Override public void setEnabled(boolean enabled) { super.setEnabled(enabled); if (enabled) { m_text.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_LIST_BACKGROUND)); } else { m_text.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_WIDGET_LIGHT_SHADOW)); } } public String getItem(int index) { checkWidget(); return m_table.getItem(index).getText(); } public int getSelectionIndex() { checkWidget(); return m_table.getSelectionIndex(); } public void removeAll() { checkWidget(); m_text.setText(""); //$NON-NLS-1$ m_text.setImage(null); m_table.removeAll(); } public int indexOf(String string) { return indexOf(string, 0); } public int indexOf(String string, int start) { checkWidget(); if (string == null) { return -1; } TableItem[] items = m_table.getItems(); for (int i = start; i < items.length; i++) { TableItem item = items[i]; if (item.getText().equalsIgnoreCase(string)) { return i; } } return -1; } public String getText() { return m_text.getText(); } public int getItemCount() { checkWidget(); return m_table.getItemCount(); } protected void setText(String string) { m_text.setText(string); } protected void setImage(Image image) { m_text.setImage(image); } public void add(String text) { add(text, null); } public void add(String text, Image image) { checkWidget(); TableItem item = new TableItem(m_table, SWT.NONE); item.setText(text); item.setImage(image); } 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); } }