| /******************************************************************************* |
| * 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.wb.draw2d.IColorConstants; |
| import org.eclipse.wb.internal.core.model.property.table.PropertyTable; |
| import org.eclipse.wb.internal.core.utils.binding.editors.controls.DefaultControlActionsManager; |
| |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.events.SelectionListener; |
| 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.Composite; |
| 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 org.eclipse.swt.widgets.Widget; |
| |
| /** |
| * Combo control for {@link PropertyTable} and combo property editors. |
| * |
| * @author scheglov_ke |
| * @coverage core.control |
| */ |
| public class CCombo3 extends Composite { |
| private final long m_createTime = System.currentTimeMillis(); |
| private final CImageLabel m_text; |
| private final Button m_arrow; |
| private final Shell m_popup; |
| private final Table m_table; |
| private boolean m_fullDropdownTableSize = false; |
| |
| //////////////////////////////////////////////////////////////////////////// |
| // |
| // Constructor |
| // |
| //////////////////////////////////////////////////////////////////////////// |
| public CCombo3(Composite parent, int style) { |
| super(parent, style); |
| addEvents(this, m_comboListener, new int[]{SWT.Dispose, SWT.Move, SWT.Resize}); |
| // create label |
| { |
| m_text = new CImageLabel(this, SWT.NONE); |
| new DefaultControlActionsManager(m_text); |
| addEvents(m_text, m_textListener, new int[]{ |
| SWT.KeyDown, |
| SWT.KeyUp, |
| SWT.MouseDown, |
| SWT.MouseUp, |
| SWT.MouseMove, |
| SWT.MouseDoubleClick, |
| SWT.Traverse, |
| SWT.FocusIn, |
| SWT.FocusOut}); |
| } |
| // create arrow |
| { |
| m_arrow = new Button(this, SWT.ARROW | SWT.DOWN); |
| addEvents(m_arrow, m_arrowListener, new int[]{SWT.Selection, SWT.FocusIn, SWT.FocusOut}); |
| } |
| // create popup Shell |
| { |
| Shell shell = getShell(); |
| m_popup = new Shell(shell, SWT.NONE); |
| m_popup.setLayout(new FillLayout()); |
| } |
| // create table for items |
| { |
| m_table = new Table(m_popup, SWT.FULL_SELECTION); |
| addEvents(m_table, m_tableListener, new int[]{SWT.Selection, SWT.FocusIn, SWT.FocusOut}); |
| // |
| new TableColumn(m_table, SWT.NONE); |
| } |
| // Focus tracking filter |
| { |
| final Listener filter = new Listener() { |
| private boolean hasFocus; |
| |
| public void handleEvent(Event event) { |
| boolean old_hasFocus = hasFocus; |
| hasFocus = |
| m_text.isFocusControl() |
| || m_arrow.isFocusControl() |
| || m_popup.isFocusControl() |
| || m_table.isFocusControl(); |
| // configure colors |
| if (hasFocus) { |
| m_text.setBackground(IColorConstants.listSelection); |
| m_text.setForeground(IColorConstants.listSelectionText); |
| } else { |
| m_text.setBackground(IColorConstants.listBackground); |
| m_text.setForeground(IColorConstants.listForeground); |
| } |
| // send FocusOut event |
| if (old_hasFocus && !hasFocus) { |
| Event e = new Event(); |
| e.widget = CCombo3.this; |
| e.time = event.time; |
| notifyListeners(SWT.FocusOut, e); |
| } |
| } |
| }; |
| getDisplay().addFilter(SWT.FocusIn, filter); |
| addListener(SWT.Dispose, new Listener() { |
| public void handleEvent(Event event) { |
| getDisplay().removeFilter(SWT.FocusIn, filter); |
| } |
| }); |
| } |
| } |
| |
| //////////////////////////////////////////////////////////////////////////// |
| // |
| // Events handling |
| // |
| //////////////////////////////////////////////////////////////////////////// |
| private final Listener m_comboListener = new Listener() { |
| public void handleEvent(Event event) { |
| switch (event.type) { |
| case SWT.Dispose : |
| if (!m_popup.isDisposed()) { |
| m_popup.dispose(); |
| } |
| break; |
| case SWT.Move : |
| doDropDown(false); |
| break; |
| case SWT.Resize : |
| doResize(); |
| break; |
| } |
| } |
| }; |
| private final Listener m_textListener = new Listener() { |
| public void handleEvent(final Event event) { |
| switch (event.type) { |
| case SWT.MouseDown : |
| if (System.currentTimeMillis() - m_createTime < 400) { |
| // send "logical" double click for case when we just activated combo |
| // and almost right away click second time (but first time on editor) |
| event.detail = -1; |
| notifyListeners(SWT.MouseDoubleClick, event); |
| // when we use "auto drop on editor activation" option, this click is |
| // is "logically" second one, so it should close combo |
| if (!isDisposed()) { |
| doDropDown(false); |
| } |
| } else { |
| m_text.setCapture(true); |
| doDropDown(!isDropped()); |
| } |
| break; |
| case SWT.MouseUp : { |
| m_text.setCapture(false); |
| TableItem item = getItemUnderCursor(event); |
| if (item != null) { |
| doDropDown(false); |
| sendSelectionEvent(event); |
| } |
| break; |
| } |
| case SWT.MouseDoubleClick : |
| // prevent resending MouseDoubleClick that we sent on fast MouseDown |
| if (event.detail != -1) { |
| notifyListeners(SWT.MouseDoubleClick, event); |
| } |
| break; |
| case SWT.MouseMove : { |
| TableItem item = getItemUnderCursor(event); |
| if (item != null) { |
| m_table.setSelection(new TableItem[]{item}); |
| } |
| break; |
| } |
| case SWT.KeyDown : { |
| // check for keyboard navigation and selection |
| { |
| int selectionIndex = m_table.getSelectionIndex(); |
| if (event.keyCode == SWT.ARROW_UP) { |
| selectionIndex--; |
| if (selectionIndex < 0) { |
| selectionIndex = m_table.getItemCount() - 1; |
| } |
| m_table.setSelection(selectionIndex); |
| return; |
| } else if (event.keyCode == SWT.ARROW_DOWN) { |
| m_table.setSelection((selectionIndex + 1) % m_table.getItemCount()); |
| return; |
| } else if (event.character == SWT.CR || event.character == ' ') { |
| sendSelectionEvent(event); |
| return; |
| } |
| } |
| // be default just resend event |
| resendKeyEvent(event); |
| break; |
| } |
| case SWT.KeyUp : |
| resendKeyEvent(event); |
| break; |
| } |
| } |
| |
| private TableItem getItemUnderCursor(Event event) { |
| Point displayLocation = m_text.toDisplay(new Point(event.x, event.y)); |
| Point tableLocation = m_table.toControl(displayLocation); |
| return m_table.getItem(tableLocation); |
| } |
| }; |
| private final Listener m_arrowListener = new Listener() { |
| public void handleEvent(Event event) { |
| switch (event.type) { |
| /*case SWT.FocusIn : { |
| resendFocusEvent(event); |
| break; |
| }*/ |
| case SWT.Selection : { |
| doDropDown(!isDropped()); |
| break; |
| } |
| } |
| } |
| }; |
| private final Listener m_tableListener = new Listener() { |
| public void handleEvent(Event event) { |
| switch (event.type) { |
| case SWT.Selection : { |
| doDropDown(false); |
| // show selected item in text |
| { |
| int index = m_table.getSelectionIndex(); |
| select(index); |
| } |
| // send selection event |
| sendSelectionEvent(event); |
| break; |
| } |
| } |
| } |
| }; |
| |
| //////////////////////////////////////////////////////////////////////////// |
| // |
| // Events utils |
| // |
| //////////////////////////////////////////////////////////////////////////// |
| /** |
| * Sends selection event. |
| */ |
| private void sendSelectionEvent(Event event) { |
| Event e = new Event(); |
| e.time = event.time; |
| e.stateMask = event.stateMask; |
| notifyListeners(SWT.Selection, e); |
| } |
| |
| /** |
| * Resends KeyDown/KeyUp events. |
| */ |
| private void resendKeyEvent(Event event) { |
| Event e = new Event(); |
| e.time = event.time; |
| e.character = event.character; |
| e.keyCode = event.keyCode; |
| e.stateMask = event.stateMask; |
| notifyListeners(event.type, e); |
| } |
| |
| /** |
| * Adds given listener as handler for events in given widget. |
| */ |
| private void addEvents(Widget widget, Listener listener, int[] events) { |
| for (int i = 0; i < events.length; i++) { |
| widget.addListener(events[i], listener); |
| } |
| } |
| |
| /** |
| * 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); |
| } |
| |
| //////////////////////////////////////////////////////////////////////////// |
| // |
| // Activity |
| // |
| //////////////////////////////////////////////////////////////////////////// |
| /** |
| * Sets drop state of combo. |
| */ |
| public void doDropDown(boolean drop) { |
| // check, may be we already in this drop state |
| if (drop == isDropped()) { |
| return; |
| } |
| // close combo |
| if (!drop) { |
| m_popup.setVisible(false); |
| m_text.setFocus(); |
| return; |
| } |
| // open combo |
| { |
| // prepare popup location |
| Point comboSize = getSize(); |
| Point popupLocation; |
| { |
| //popupLocation = getParent().toDisplay(getLocation()); |
| popupLocation = toDisplay(new Point(0, 0)); |
| popupLocation.y += comboSize.y; |
| } |
| // calculate and set popup location |
| { |
| TableColumn tableColumn = m_table.getColumn(0); |
| // pack everything |
| tableColumn.pack(); |
| m_table.pack(); |
| m_popup.pack(); |
| // calculate bounds |
| Rectangle tableBounds = m_table.getBounds(); |
| tableBounds.height = Math.min(tableBounds.height, m_table.getItemHeight() * 20); // max 20 items without scrolling |
| m_table.setBounds(tableBounds); |
| // calculate size |
| int remainingDisplayHeight = getDisplay().getClientArea().height - popupLocation.y - 10; |
| int preferredHeight = Math.min(tableBounds.height, remainingDisplayHeight); |
| int remainingDisplayWidth = getDisplay().getClientArea().width - popupLocation.x - 5; |
| int preferredWidth = |
| isFullDropdownTableWidth() |
| ? Math.min(tableBounds.width, remainingDisplayWidth) |
| : comboSize.x; |
| // set popup bounds calculated as computeTrim basing on combo width and table height paying attention on remaining display space |
| Rectangle popupBounds = |
| m_popup.computeTrim(popupLocation.x, popupLocation.y, preferredWidth, preferredHeight); |
| m_popup.setBounds(popupBounds); |
| // adjust column size |
| tableColumn.setWidth(m_table.getClientArea().width); |
| } |
| m_popup.setVisible(true); |
| // scroll to selection if needed |
| m_table.showSelection(); |
| } |
| } |
| |
| /** |
| * Initiates "press-hold-drag" sequence. |
| */ |
| public void startDrag() { |
| m_text.setCapture(true); |
| } |
| |
| //////////////////////////////////////////////////////////////////////////// |
| // |
| // Access |
| // |
| //////////////////////////////////////////////////////////////////////////// |
| public void setFullDropdownTableWidth(boolean freeTableSize) { |
| m_fullDropdownTableSize = freeTableSize; |
| } |
| |
| public boolean isFullDropdownTableWidth() { |
| return m_fullDropdownTableSize; |
| } |
| |
| public boolean isDropped() { |
| return m_popup.isVisible(); |
| } |
| |
| public void setQuickSearch(boolean value) { |
| // TODO |
| } |
| |
| //////////////////////////////////////////////////////////////////////////// |
| // |
| // Access: items |
| // |
| //////////////////////////////////////////////////////////////////////////// |
| /** |
| * Removes all items. |
| */ |
| public void removeAll() { |
| TableItem[] items = m_table.getItems(); |
| for (int index = 0; index < items.length; index++) { |
| TableItem item = items[index]; |
| item.dispose(); |
| } |
| } |
| |
| /** |
| * Adds new item with given text. |
| */ |
| public void add(String text) { |
| add(text, null); |
| } |
| |
| /** |
| * Adds new item with given text and image. |
| */ |
| public void add(String text, Image image) { |
| checkWidget(); |
| TableItem item = new TableItem(m_table, SWT.NONE); |
| item.setText(text); |
| item.setImage(image); |
| } |
| |
| /** |
| * @return an item at given index |
| */ |
| public String getItem(int index) { |
| checkWidget(); |
| return m_table.getItem(index).getText(); |
| } |
| |
| /** |
| * @return the number of items |
| */ |
| public int getItemCount() { |
| checkWidget(); |
| return m_table.getItemCount(); |
| } |
| |
| /** |
| * @return the index of the selected item |
| */ |
| public int getSelectionIndex() { |
| checkWidget(); |
| return m_table.getSelectionIndex(); |
| } |
| |
| /** |
| * Selects an item with given index. |
| */ |
| public void select(int index) { |
| checkWidget(); |
| if (index == -1) { |
| m_table.deselectAll(); |
| m_text.setText(null); |
| m_text.setImage(null); |
| return; |
| } else { |
| TableItem item = m_table.getItem(index); |
| m_text.setText(item.getText()); |
| m_text.setImage(item.getImage()); |
| m_table.select(index); |
| } |
| } |
| |
| //////////////////////////////////////////////////////////////////////////// |
| // |
| // Access: text and image |
| // |
| //////////////////////////////////////////////////////////////////////////// |
| /** |
| * Selects item with given text. |
| */ |
| public void setText(String text) { |
| // try to find item with given text |
| TableItem[] items = m_table.getItems(); |
| for (int index = 0; index < items.length; index++) { |
| TableItem item = items[index]; |
| if (item.getText().equals(text)) { |
| select(index); |
| return; |
| } |
| } |
| // not found, remove selection |
| select(-1); |
| } |
| |
| //////////////////////////////////////////////////////////////////////////// |
| // |
| // Resize support |
| // TODO: computeSize |
| // |
| //////////////////////////////////////////////////////////////////////////// |
| protected void doResize() { |
| Rectangle clientArea = getClientArea(); |
| int areaWidth = clientArea.width; |
| int areaHeight = clientArea.height; |
| // compute sizes of controls |
| Point buttonSize = m_arrow.computeSize(areaHeight, areaHeight); |
| Point textSize = m_text.computeSize(areaWidth - buttonSize.x, areaHeight); |
| // set controls location/size |
| m_arrow.setLocation(areaWidth - buttonSize.x, 0); |
| m_arrow.setSize(buttonSize); |
| m_text.setSize(areaWidth - buttonSize.x, Math.max(textSize.y, areaHeight)); |
| } |
| } |