Package org.eclipse.nebula.widgets.nattable.widget

Source Code of org.eclipse.nebula.widgets.nattable.widget.NatCombo$FocusListenerWrapper

/*******************************************************************************
* Copyright (c) 2012, 2013 Original authors and others.
* 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:
*     Original authors and others - initial API and implementation
******************************************************************************/
package org.eclipse.nebula.widgets.nattable.widget;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.eclipse.nebula.widgets.nattable.style.CellStyleAttributes;
import org.eclipse.nebula.widgets.nattable.style.CellStyleUtil;
import org.eclipse.nebula.widgets.nattable.style.HorizontalAlignmentEnum;
import org.eclipse.nebula.widgets.nattable.style.IStyle;
import org.eclipse.nebula.widgets.nattable.style.VerticalAlignmentEnum;
import org.eclipse.nebula.widgets.nattable.ui.matcher.LetterOrDigitKeyEventMatcher;
import org.eclipse.nebula.widgets.nattable.util.GUIHelper;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.ControlListener;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.FocusListener;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.KeyListener;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseListener;
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.ShellListener;
import org.eclipse.swt.events.TraverseListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.GC;
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.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
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;

/**
* Customized combobox control that supports editing directly in the text field
* and selecting items from the dropdown.
*
* <p>
* This control supports the ability for multi select in the dropdown of the
* combo which is not available for the SWT Combo control. This feature was
* added with Nebula NatTable 1.0.0
*
* <p>
* The following style bits are supported by this control.
*
* @see SWT#BORDER (if a border should be added to the Text control)
* @see SWT#READ_ONLY (default for Text control, if this is missing, the Text
*      control can be edited)
* @see SWT#CHECK (if the items in the combo should be showed with checkboxes)
* @see SWT#MULTI (if multi selection is allowed)
*/
public class NatCombo extends Composite {

    /**
     * Default String that is used to separate values in the String
     * representation showed in the text control if multiselect is supported.
     */
    public static final String DEFAULT_MULTI_SELECT_VALUE_SEPARATOR = ", "; //$NON-NLS-1$
    /**
     * Default String that is used to prefix the generated String representation
     * showed in the text control if multiselect is supported.
     */
    public static final String DEFAULT_MULTI_SELECT_PREFIX = "["; //$NON-NLS-1$
    /**
     * String that is used to suffix the generated String representation showed
     * in the text control if multiselect is supported.
     */
    public static final String DEFAULT_MULTI_SELECT_SUFFIX = "]"; //$NON-NLS-1$
    /**
     * The default number of visible items on open the combo.
     */
    public static final int DEFAULT_NUM_OF_VISIBLE_ITEMS = 5;

    /**
     * The IStyle that is used for rendering the Text and the combo control. The
     * important configurations used are horizontal alignment, background and
     * foreground color and font.
     */
    protected final IStyle cellStyle;

    /**
     * The maximum number of visible items of the combo. Setting this value to
     * -1 will result in always showing all items at once.
     */
    protected int maxVisibleItems;

    /**
     * The items that are showed within the combo transformed to a
     * java.util.List. Needed for indexed operations in the dropdown
     */
    protected java.util.List<String> itemList;

    /**
     * The text control of this NatCombo, allowing to enter values directly.
     */
    protected Text text;

    /**
     * The Shell containing the dropdown of this NatCombo
     */
    protected Shell dropdownShell;

    /**
     * The Table control used for the combo component of this NatCombo
     */
    protected Table dropdownTable;

    /**
     * The image that is shown at the right edge of the text control if the
     * NatCombo is opened.
     */
    protected Image iconImage;

    /**
     * The style bits that where set on creation time. Needed in case the
     * dropdown shell was disposed and needs to be created again.
     */
    protected final int style;

    /**
     * Flag that indicates whether this ComboBoxCellEditor supports free editing
     * in the text control of the NatCombo or not. By default free editing is
     * disabled.
     */
    protected boolean freeEdit;

    /**
     * Flag that indicates whether this NatCombo supports multiselect or not. By
     * default multiselect is disabled.
     */
    protected boolean multiselect;

    /**
     * Flag that indicates whether checkboxes should be shown for the items in
     * the dropdown.
     */
    protected boolean useCheckbox;

    /**
     * String that is used to separate values in the String representation
     * showed in the text control if multiselect is supported.
     */
    protected String multiselectValueSeparator = DEFAULT_MULTI_SELECT_VALUE_SEPARATOR;
    /**
     * String that is used to prefix the generated String representation showed
     * in the text control if multiselect is supported. Needed to visualize the
     * multiselection to the user.
     */
    protected String multiselectTextPrefix = DEFAULT_MULTI_SELECT_PREFIX;
    /**
     * String that is used to suffix the generated String representation showed
     * in the text control if multiselect is supported. Needed to visualize the
     * multiselection to the user.
     */
    protected String multiselectTextSuffix = DEFAULT_MULTI_SELECT_SUFFIX;

    /**
     * Flag that tells whether the NatCombo has focus or not. The flag is set by
     * the FocusListenerWrapper that is set as focus listener on both, the Text
     * control and the dropdown table control. This flag is necessary as the
     * NatCombo has focus if either of both controls have focus.
     */
    private boolean hasFocus = false;
    /**
     * Flag to determine whether the focus lost runnable is currently active or
     * not. Necessary in case the FocusListener is removing itself on
     * focusLost(). Quite unusual in normal cases, but for NatTable editing this
     * appears because if the control looses focus it gets destroyed in
     * AbstractCellEditor.close() Introducing and handling this flag ensures
     * concurrency safety.
     */
    private boolean focusLostRunnableActive = false;
    /**
     * The list of FocusListener that contains the listeners that will be
     * informed if the NatCombo control gains or looses focus. We keep our own
     * list of listeners because the two controls that are combined in this
     * control share the same focus.
     */
    private List<FocusListener> focusListener = new ArrayList<FocusListener>();

    /**
     * Creates a new NatCombo using the given IStyle for rendering, showing the
     * default number of items at once in the dropdown. Creating the NatCombo
     * with this constructor, there is no free edit and no multiple selection
     * enabled.
     *
     * @param parent
     *            A widget that will be the parent of this NatCombo
     * @param cellStyle
     *            Style configuration containing horizontal alignment, font,
     *            foreground and background color information.
     * @param style
     *            The style for the Text Control to construct. Uses this style
     *            adding internal styles via ConfigRegistry.
     */
    public NatCombo(Composite parent, IStyle cellStyle, int style) {
        this(parent, cellStyle, DEFAULT_NUM_OF_VISIBLE_ITEMS, style, GUIHelper
                .getImage("down_2")); //$NON-NLS-1$
    }

    /**
     * Creates a new NatCombo using the given IStyle for rendering, showing the
     * given amount of items at once in the dropdown. Creating the NatCombo with
     * this constructor, there is no free edit and no multiple selection
     * enabled.
     *
     * @param parent
     *            A widget that will be the parent of this NatCombo
     * @param cellStyle
     *            Style configuration containing horizontal alignment, font,
     *            foreground and background color information.
     * @param maxVisibleItems
     *            the max number of items the drop down will show before
     *            introducing a scroll bar.
     * @param style
     *            The style for the Text Control to construct. Uses this style
     *            adding internal styles via ConfigRegistry.
     */
    public NatCombo(Composite parent, IStyle cellStyle, int maxVisibleItems,
            int style) {
        this(parent, cellStyle, maxVisibleItems, style, GUIHelper
                .getImage("down_2")); //$NON-NLS-1$
    }

    /**
     * Creates a new NatCombo using the given IStyle for rendering, showing the
     * given amount of items at once in the dropdown.
     *
     * @param parent
     *            A widget that will be the parent of this NatCombo
     * @param cellStyle
     *            Style configuration containing horizontal alignment, font,
     *            foreground and background color information.
     * @param maxVisibleItems
     *            the max number of items the drop down will show before
     *            introducing a scroll bar.
     * @param style
     *            The style for the {@link Text} Control to construct. Uses this
     *            style adding internal styles via ConfigRegistry.
     * @param iconImage
     *            The image to use as overlay to the {@link Text} Control if the
     *            dropdown is visible. Using this image will indicate that the
     *            control is an open combo to the user.
     */
    public NatCombo(Composite parent, IStyle cellStyle, int maxVisibleItems,
            int style, Image iconImage) {
        super(parent, SWT.NONE);

        this.cellStyle = cellStyle;
        this.maxVisibleItems = maxVisibleItems;
        this.iconImage = iconImage;

        this.style = style;

        this.freeEdit = (style & SWT.READ_ONLY) == 0;
        this.multiselect = (style & SWT.MULTI) != 0;
        this.useCheckbox = (style & SWT.CHECK) != 0;

        GridLayout gridLayout = new GridLayout(2, false);
        gridLayout.marginWidth = 0;
        gridLayout.marginHeight = 0;
        gridLayout.horizontalSpacing = 0;
        setLayout(gridLayout);

        createTextControl(style);
        createDropdownControl(style);

        addDisposeListener(new DisposeListener() {
            @Override
            public void widgetDisposed(DisposeEvent e) {
                dropdownShell.dispose();
                text.dispose();
            }
        });

        // typically the dropdown shell should be hidden when the focus is lost
        // but in case the NatCombo is the first control in a shell, the text
        // control
        // will get the focus immediately after the shell lost focus.
        // as handling with focus listeners in such a case fails, we add a move
        // listener
        // that will update the position of the dropdown shell if the parent
        // shell moves
        getShell().addListener(SWT.Move, new Listener() {
            @Override
            public void handleEvent(Event event) {
                calculateBounds();
            }
        });
    }

    /**
     * Sets the given items to be the items shown in the dropdown of this
     * NatCombo.
     *
     * @param items
     *            The array of items to set.
     */
    public void setItems(String[] items) {
        if (items != null) {
            this.itemList = Arrays.asList(items);
            if (!dropdownTable.isDisposed()) {
                for (String item : items) {
                    TableItem tableItem = new TableItem(dropdownTable, SWT.NONE);
                    tableItem.setText(item);
                }
            }
        }
    }

    /**
     * Creates the Text control of this NatCombo, adding styles, look&amp;feel
     * and needed listeners for the control only.
     *
     * @param style
     *            The style for the Text Control to construct. Uses this style
     *            adding internal styles via ConfigRegistry.
     */
    protected void createTextControl(int style) {
        int widgetStyle = style
                | HorizontalAlignmentEnum.getSWTStyle(cellStyle);
        text = new Text(this, widgetStyle);
        text.setBackground(cellStyle
                .getAttributeValue(CellStyleAttributes.BACKGROUND_COLOR));
        text.setForeground(cellStyle
                .getAttributeValue(CellStyleAttributes.FOREGROUND_COLOR));
        text.setFont(cellStyle.getAttributeValue(CellStyleAttributes.FONT));

        GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, true);
        text.setLayoutData(gridData);

        text.addKeyListener(new KeyAdapter() {

            @Override
            public void keyPressed(KeyEvent event) {
                if (event.keyCode == SWT.ARROW_DOWN
                        || event.keyCode == SWT.ARROW_UP) {
                    showDropdownControl();

                    int selectionIndex = dropdownTable.getSelectionIndex();
                    if (selectionIndex < 0)
                        selectionIndex = 0;
                    dropdownTable.select(selectionIndex);

                    // ensure the arrow key events do not have any further
                    // effect
                    event.doit = false;
                } else if (!LetterOrDigitKeyEventMatcher
                        .isLetterOrDigit(event.character)) {
                    if (freeEdit) {
                        // simply clear the selection in dropdownlist so the
                        // free value in text control
                        // will be used
                        if (!dropdownTable.isDisposed()) {
                            dropdownTable.deselectAll();
                        }
                    } else {
                        showDropdownControl();
                    }
                }
            }
        });

        text.addMouseListener(new MouseAdapter() {

            @Override
            public void mouseDown(MouseEvent e) {
                if (!freeEdit) {
                    if (dropdownTable.isDisposed()
                            || !dropdownTable.isVisible()) {
                        showDropdownControl();
                    } else {
                        // if there is no free edit enabled, set the focus back
                        // to the
                        // dropdownlist so it handles key strokes itself
                        dropdownTable.forceFocus();
                    }
                }
            }
        });

        text.addControlListener(new ControlListener() {
            @Override
            public void controlResized(ControlEvent e) {
                calculateBounds();
            }

            @Override
            public void controlMoved(ControlEvent e) {
                calculateBounds();
            }
        });

        text.addFocusListener(new FocusListenerWrapper());

        final Canvas iconCanvas = new Canvas(this, SWT.NONE) {

            @Override
            public Point computeSize(int wHint, int hHint, boolean changed) {
                Rectangle iconImageBounds = iconImage.getBounds();
                return new Point(iconImageBounds.width + 2,
                        iconImageBounds.height + 2);
            }

        };

        gridData = new GridData(GridData.BEGINNING, SWT.FILL, false, true);
        iconCanvas.setLayoutData(gridData);

        iconCanvas.addPaintListener(new PaintListener() {

            @Override
            public void paintControl(PaintEvent event) {
                GC gc = event.gc;

                Rectangle iconCanvasBounds = iconCanvas.getBounds();
                Rectangle iconImageBounds = iconImage.getBounds();
                int horizontalAlignmentPadding = CellStyleUtil
                        .getHorizontalAlignmentPadding(
                                HorizontalAlignmentEnum.CENTER,
                                iconCanvasBounds, iconImageBounds.width);
                int verticalAlignmentPadding = CellStyleUtil
                        .getVerticalAlignmentPadding(
                                VerticalAlignmentEnum.MIDDLE, iconCanvasBounds,
                                iconImageBounds.height);
                gc.drawImage(iconImage, horizontalAlignmentPadding,
                        verticalAlignmentPadding);

                Color originalFg = gc.getForeground();
                gc.setForeground(GUIHelper.COLOR_WIDGET_BORDER);
                gc.drawRectangle(0, 0, iconCanvasBounds.width - 1,
                        iconCanvasBounds.height - 1);
                gc.setForeground(originalFg);
            }

        });

        iconCanvas.addMouseListener(new MouseAdapter() {

            @Override
            public void mouseDown(MouseEvent e) {
                if (dropdownShell != null && !dropdownShell.isDisposed()) {
                    if (dropdownShell.isVisible()) {
                        text.forceFocus();
                        hideDropdownControl();
                    } else {
                        showDropdownControl();
                    }
                }
            }
        });
    }

    /**
     * Create the dropdown control of this NatCombo, adding styles,
     * look&amp;feel and needed listeners for the control only.
     *
     * @param style
     *            The style for the Table Control to construct. Uses this style
     *            adding internal styles via ConfigRegistry.
     */
    protected void createDropdownControl(int style) {
        dropdownShell = new Shell(getShell(), SWT.MODELESS);
        dropdownShell.setLayout(new FillLayout());

        int dropdownListStyle = style | SWT.V_SCROLL
                | HorizontalAlignmentEnum.getSWTStyle(cellStyle)
                | SWT.FULL_SELECTION;

        dropdownTable = new Table(dropdownShell, dropdownListStyle);
        dropdownTable.setBackground(cellStyle
                .getAttributeValue(CellStyleAttributes.BACKGROUND_COLOR));
        dropdownTable.setForeground(cellStyle
                .getAttributeValue(CellStyleAttributes.FOREGROUND_COLOR));
        dropdownTable.setFont(cellStyle
                .getAttributeValue(CellStyleAttributes.FONT));

        // add a column to be able to resize the item width in the dropdown
        new TableColumn(dropdownTable, SWT.NONE);
        dropdownTable.addListener(SWT.Resize, new Listener() {
            @Override
            public void handleEvent(Event event) {
                calculateColumnWidth();
            }
        });

        dropdownTable.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                boolean selected = e.detail != SWT.CHECK;
                TableItem item = (TableItem) e.item;

                // checkbox clicked, now sync the selection
                if (!selected) {
                    if (!item.getChecked()) {
                        dropdownTable.deselect(itemList.indexOf(item.getText()));
                    } else {
                        dropdownTable.select(itemList.indexOf(item.getText()));
                    }
                }
                // item selected, now sync checkbox
                else if (useCheckbox) {
                    // after selection is performed we need to ensure that
                    // selection and checkboxes are in sync
                    for (TableItem tableItem : dropdownTable.getItems()) {
                        tableItem.setChecked(dropdownTable.isSelected(itemList
                                .indexOf(tableItem.getText())));
                    }
                }

                updateTextControl(false);
            }
        });

        dropdownTable.addKeyListener(new KeyAdapter() {
            @Override
            public void keyPressed(KeyEvent event) {
                if ((event.keyCode == SWT.CR)
                        || (event.keyCode == SWT.KEYPAD_CR)) {
                    updateTextControl(true);
                } else if (event.keyCode == SWT.F2 && freeEdit) {
                    text.forceFocus();
                    hideDropdownControl();
                }
            }
        });

        dropdownTable.addFocusListener(new FocusListenerWrapper());

        if (this.itemList != null) {
            setItems(this.itemList.toArray(new String[] {}));
        }
        setDropdownSelection(getTextAsArray());
    }

    /**
     * This method will be called if an item of the dropdown control is selected
     * via mouse click or pressing enter. It will populate the text control with
     * the information gathered out of the selection in the dropdown control and
     * hide the dropdown if necessary.
     *
     * @param hideDropdown
     *            <code>true</code> if the dropdown should be hidden after
     *            updating the text control
     */
    protected void updateTextControl(boolean hideDropdown) {
        text.setText(getTransformedTextForSelection());
        if (hideDropdown) {
            hideDropdownControl();
        }
    }

    /**
     * Shows the dropdown of this NatCombo. Will always calculate the size of
     * the dropdown regarding the current size of the Text control.
     */
    public void showDropdownControl() {
        showDropdownControl(false);
    }

    /**
     * Shows the dropdown of this NatCombo. Will always calculate the size of
     * the dropdown regarding the current size of the Text control.
     *
     * @param focusOnText
     *            <code>true</code> if the focus should be set to the text
     *            control instead of the dropdown after opening the dropdown.
     */
    public void showDropdownControl(boolean focusOnText) {
        if (dropdownShell.isDisposed()) {
            createDropdownControl(this.style);
        }
        calculateBounds();
        dropdownShell.open();
        if (focusOnText) {
            this.text.forceFocus();
            this.text.setSelection(this.text.getText().length());
        }
    }

    /**
     * Hide the dropdown of this NatCombo.
     */
    public void hideDropdownControl() {
        if (!dropdownShell.isDisposed()) {
            dropdownShell.setVisible(false);
        }
    }

    /**
     * Calculates the number of items that should be showed in the dropdown at
     * once. It is needed to calculate the height of the dropdown. If
     * maxVisibleItems is configured -1, this method always returns the number
     * of items in the list. Otherwise if will return the configured maximum
     * number of items to be visible at once or less if there are less than the
     * configured maximum.
     *
     * @return the number of items that should be showed in the dropdown at
     *         once.
     */
    protected int getVisibleItemCount() {
        int itemCount = dropdownTable.getItemCount();
        if (itemCount > 0) {
            // if maxVisibleItems == -1 show all items at once
            // otherwise use the minimum for item count or max visible item
            // configuration
            int visibleItemCount = itemCount;
            if (this.maxVisibleItems > 0) {
                visibleItemCount = Math.min(itemCount, maxVisibleItems);
            }
            itemCount = visibleItemCount;
        }
        return itemCount;
    }

    /**
     * Calculates the size and location of the Shell that represents the
     * dropdown control of this NatCombo. Size and location will be calculated
     * dependent the position and size of the corresponding Text control and the
     * information showed in the dropdown.
     */
    protected void calculateBounds() {
        if (dropdownShell != null && !dropdownShell.isDisposed()) {
            Point size = getSize();
            // calculate the height by multiplying the number of visible items
            // with
            // the item height of items in the list and adding 2*grid line width
            // to work around a
            // calculation error regarding the descent of the font metrics for
            // the
            // last shown item
            // Note: if there are no items to show in the combo, calculate with
            // the item count of
            // 3 so an empty combo will open
            int listHeight = (getVisibleItemCount() > 0 ? getVisibleItemCount()
                    : 3)
                    * dropdownTable.getItemHeight()
                    + dropdownTable.getGridLineWidth() * 2;

            // since introduced the TableColumn for real full row selection, we
            // call pack() to
            // perform autoresize to ensure the width shows the whole content
            dropdownTable.getColumn(0).pack();
            int listWidth = Math.max(
                    dropdownTable.computeSize(SWT.DEFAULT, listHeight, true).x,
                    size.x);

            // correction of the shell bounds to ensure the scrollbars are shown
            // full
            int correction = 0;
            if (this.maxVisibleItems > 0
                    && getVisibleItemCount() < dropdownTable.getItemCount()) {
                correction = 2;
            }
            dropdownTable.setSize(listWidth - correction, listHeight
                    - correction);

            calculateColumnWidth();

            Point textPosition = text.toDisplay(text.getLocation());

            // by default the dropdown shell will be created below the cell in
            // the table
            int dropdownShellStartingY = textPosition.y
                    + text.getBounds().height;
            int shellBottomY = textPosition.y + text.getBounds().height
                    + listHeight;
            // if the bottom of the drowdown is below the display, render it
            // above the cell
            if (shellBottomY > Display.getCurrent().getBounds().height) {
                dropdownShellStartingY = textPosition.y - listHeight;
            }

            Rectangle shellBounds = new Rectangle(textPosition.x,
                    dropdownShellStartingY, listWidth, listHeight);

            dropdownShell.setBounds(shellBounds);
        }
    }

    /**
     * Calculates and applies the column width to ensure that the column has the
     * same width as the table itself, so selection is possible for the whole
     * row.
     */
    protected void calculateColumnWidth() {
        int width = dropdownTable.getBounds().width;
        if (dropdownTable.getVerticalBar() != null && maxVisibleItems > -1
                && dropdownTable.getItemCount() > maxVisibleItems) {
            width -= dropdownTable.getVerticalBar().getSize().x;
        } else {
            // remove the left and the right grid line width so the column does
            // not exceed the table
            width -= dropdownTable.getGridLineWidth() * 2;
        }
        dropdownTable.getColumn(0).setWidth(width);
    }

    /**
     * Returns the zero-relative index of the item which is currently selected
     * in the receiver, or -1 if no item is selected.
     * <p>
     * Note that this only returns useful results if this NatCombo supports
     * single selection or only one item is selected.
     *
     * @return the index of the selected item or -1
     */
    public int getSelectionIndex() {
        if (!this.dropdownTable.isDisposed()) {
            return this.dropdownTable.getSelectionIndex();
        } else if (!this.text.isDisposed()) {
            return this.itemList.indexOf(this.text.getText());
        }
        return -1;
    }

    /**
     * Returns the zero-relative indices of the items which are currently
     * selected in the receiver. The order of the indices is unspecified. The
     * array is empty if no items are selected.
     * <p>
     * Note: This is not the actual structure used by the receiver to maintain
     * its selection, so modifying the array will not affect the receiver.
     * </p>
     *
     * @return the array of indices of the selected items
     */
    public int[] getSelectionIndices() {
        if (!this.dropdownTable.isDisposed()) {
            return this.dropdownTable.getSelectionIndices();
        } else {
            String[] selectedItems = getTextAsArray();
            int[] result = new int[selectedItems.length];
            for (int i = 0; i < selectedItems.length; i++) {
                result[i] = this.itemList.indexOf(selectedItems[i]);
            }
            return result;
        }
    }

    /**
     * Returns the number of selected items contained in the receiver.
     *
     * @return the number of selected items
     */
    public int getSelectionCount() {
        if (!this.dropdownTable.isDisposed()) {
            return this.dropdownTable.getSelectionCount();
        } else {
            return getTextAsArray().length;
        }
    }

    /**
     * Returns an array of <code>String</code>s that are currently selected in
     * the receiver. The order of the items is unspecified. An empty array
     * indicates that no items are selected.
     * <p>
     * Note: This is not the actual structure used by the receiver to maintain
     * its selection, so modifying the array will not affect the receiver.
     * </p>
     *
     * @return an array representing the selection
     */
    public String[] getSelection() {
        String[] result = getTransformedSelection();
        if (result == null
                || (result.length == 0 && this.text.getText().length() > 0)) {
            result = getTextAsArray();
        }
        return result;
    }

    /**
     * Selects the items at the given zero-relative indices in the receiver. The
     * current selection is cleared before the new items are selected.
     * <p>
     * Indices that are out of range and duplicate indices are ignored. If the
     * receiver is single-select and multiple indices are specified, then all
     * indices are ignored.
     * <p>
     * The text control of this NatCombo will also be updated with the new
     * selected values.
     *
     * @param items
     *            the items to select
     */
    public void setSelection(String[] items) {
        String textValue = ""; //$NON-NLS-1$
        if (items != null) {
            if (!this.dropdownTable.isDisposed()) {
                setDropdownSelection(items);
                if (this.freeEdit
                        && this.dropdownTable.getSelectionCount() == 0) {
                    textValue = getTransformedText(items);
                } else {
                    textValue = getTransformedTextForSelection();
                }
            } else {
                textValue = getTransformedText(items);
            }
        }
        this.text.setText(textValue);
    }

    /**
     * Selects the item at the given zero-relative index in the receiver's list.
     * If the item at the index was already selected, it remains selected.
     * Indices that are out of range are ignored.
     *
     * @param index
     *            the index of the item to select
     */
    public void select(int index) {
        if (!this.dropdownTable.isDisposed()) {
            this.dropdownTable.select(index);
            this.text.setText(getTransformedTextForSelection());
        } else if (index >= 0) {
            this.text.setText(this.itemList.get(index));
        }
    }

    /**
     * Selects the items at the given zero-relative indices in the receiver. The
     * current selection is not cleared before the new items are selected.
     * <p>
     * If the item at a given index is not selected, it is selected. If the item
     * at a given index was already selected, it remains selected. Indices that
     * are out of range and duplicate indices are ignored. If the receiver is
     * single-select and multiple indices are specified, then all indices are
     * ignored.
     *
     * @param indeces
     *            the array of indices for the items to select
     */
    public void select(int[] indeces) {
        if (!this.dropdownTable.isDisposed()) {
            this.dropdownTable.select(indeces);
            this.text.setText(getTransformedTextForSelection());
        } else {
            String[] selectedItems = new String[indeces.length];
            for (int i = 0; i < indeces.length; i++) {
                if (indeces[i] >= 0) {
                    selectedItems[i] = this.itemList.get(indeces[i]);
                }
            }
            this.text.setText(getTransformedText(selectedItems));
        }
    }

    @Override
    public void addKeyListener(KeyListener listener) {
        if (this.text != null && !this.text.isDisposed())
            this.text.addKeyListener(listener);
        if (this.dropdownTable != null && !this.dropdownTable.isDisposed())
            this.dropdownTable.addKeyListener(listener);
    }

    @Override
    public void removeKeyListener(KeyListener listener) {
        if (this.text != null && !this.text.isDisposed())
            this.text.removeKeyListener(listener);
        if (this.dropdownTable != null && !this.dropdownTable.isDisposed())
            this.dropdownTable.removeKeyListener(listener);
    }

    @Override
    public void addTraverseListener(TraverseListener listener) {
        if (this.text != null && !this.text.isDisposed())
            this.text.addTraverseListener(listener);
        if (this.dropdownTable != null && !this.dropdownTable.isDisposed())
            this.dropdownTable.addTraverseListener(listener);
    }

    @Override
    public void removeTraverseListener(TraverseListener listener) {
        if (this.text != null && !this.text.isDisposed())
            this.text.removeTraverseListener(listener);
        if (this.dropdownTable != null && !this.dropdownTable.isDisposed())
            this.dropdownTable.removeTraverseListener(listener);
    }

    @Override
    public void addMouseListener(MouseListener listener) {
        // only add the mouse listener to the dropdown, as clicking in the text
        // control
        // should not trigger anything else than it is handled by the text
        // control itself.
        if (this.dropdownTable != null && !this.dropdownTable.isDisposed())
            this.dropdownTable.addMouseListener(listener);
    }

    @Override
    public void removeMouseListener(MouseListener listener) {
        if (this.dropdownTable != null && !this.dropdownTable.isDisposed())
            this.dropdownTable.removeMouseListener(listener);
    }

    @Override
    public void notifyListeners(int eventType, Event event) {
        if (this.dropdownTable != null && !this.dropdownTable.isDisposed())
            this.dropdownTable.notifyListeners(eventType, event);
    }

    public void addSelectionListener(SelectionListener listener) {
        if (this.dropdownTable != null && !this.dropdownTable.isDisposed())
            this.dropdownTable.addSelectionListener(listener);
    }

    public void removeSelectionListener(SelectionListener listener) {
        if (this.dropdownTable != null && !this.dropdownTable.isDisposed())
            this.dropdownTable.removeSelectionListener(listener);
    }

    public void addShellListener(ShellListener listener) {
        if (this.dropdownShell != null && !this.dropdownShell.isDisposed())
            this.dropdownShell.addShellListener(listener);
    }

    public void removeShellListener(ShellListener listener) {
        if (this.dropdownShell != null && !this.dropdownShell.isDisposed())
            this.dropdownShell.removeShellListener(listener);
    }

    public void addTextControlListener(ControlListener listener) {
        if (this.text != null && !this.text.isDisposed())
            this.text.addControlListener(listener);
    }

    public void removeTextControlListener(ControlListener listener) {
        if (this.text != null && !this.text.isDisposed())
            this.text.removeControlListener(listener);
    }

    @Override
    public boolean isFocusControl() {
        return hasFocus;
    }

    @Override
    public boolean forceFocus() {
        return text.forceFocus();
    }

    @Override
    public void addFocusListener(FocusListener listener) {
        this.focusListener.add(listener);
    }

    @Override
    public void removeFocusListener(final FocusListener listener) {
        // The FocusListenerWrapper is executing the focusLost event
        // in a separate thread with 100ms delay to ensure that the NatComboe
        // lost focus. This is necessary because the NatCombo is a combination
        // of a text field and a table as dropdown which do not share the
        // same focus by default.
        // To avoid concurrent modifications, in case the focus lost runnable
        // is active, the removal of the focus listener is processed in a
        // new thread after the focus lost runnable is done.
        if (focusLostRunnableActive) {
            try {
                new Thread() {
                    @Override
                    public void run() {
                        focusListener.remove(listener);
                    };
                }.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        } else {
            focusListener.remove(listener);
        }
    }

    /**
     * Transforms the selection in the Table control dropdown into a String[].
     * Doing this is necessary to provide a SWT List like interface regarding
     * selections for the NatCombo.
     *
     * @return Array containing all selected TableItem text attributes
     */
    protected String[] getTransformedSelection() {
        String[] selection = null;
        if (!this.dropdownTable.isDisposed()) {
            TableItem[] selectedItems = this.dropdownTable.getSelection();
            selection = new String[selectedItems.length];
            for (int i = 0; i < selectedItems.length; i++) {
                selection[i] = selectedItems[i].getText();
            }
        }
        return selection;
    }

    /**
     * Transforms the given String array whose contents represents selected
     * items to a selection that can be handled by the underlying Table control
     * in the dropdown.
     *
     * @param selection
     *            The Strings that represent the selected items
     */
    protected void setDropdownSelection(String[] selection) {
        if (selection.length > 0) {
            java.util.List<String> selectionList = Arrays.asList(selection);
            java.util.List<TableItem> selectedItems = new ArrayList<TableItem>();
            for (TableItem item : this.dropdownTable.getItems()) {
                if (selectionList.contains(item.getText())) {
                    selectedItems.add(item);
                    if (useCheckbox) {
                        item.setChecked(true);
                    }
                }
            }
            this.dropdownTable.setSelection(selectedItems
                    .toArray(new TableItem[] {}));
        }
    }

    /**
     * Will transform the text for the Text control of this NatCombo to an array
     * of Strings. This is necessary for the multiselect feature.
     *
     * <p>
     * Note that by default the multiselect String is specified to show with
     * enclosing [] brackets and values separated by ", ". If you need to change
     * this you need to set the corresponding values in this NatCombo.
     *
     * @return The text for the Text control of this NatCombo converted to an
     *         array of Strings.
     */
    protected String[] getTextAsArray() {
        if (!this.text.isDisposed()) {
            String transform = this.text.getText();
            if (transform.length() > 0) {
                if (this.multiselect) {
                    // for multiselect the String is defined by default in
                    // format [a, b, c]
                    // the prefix and suffix for multiselect String
                    // representation need to
                    // be removed
                    // in free edit mode we need to check if the format is used
                    int prefixLength = this.multiselectTextPrefix.length();
                    int suffixLength = this.multiselectTextSuffix.length();
                    if (this.freeEdit) {
                        if (!transform.startsWith(multiselectTextPrefix)) {
                            prefixLength = 0;
                        }
                        if (!transform.endsWith(multiselectTextSuffix)) {
                            suffixLength = 0;
                        }
                    }
                    transform = transform.substring(prefixLength,
                            transform.length() - suffixLength);
                }
                // if the transform value length is still > 0, then try to split
                if (transform.length() > 0) {
                    return transform.split(this.multiselectValueSeparator);
                }
            }
        }
        return new String[] {};
    }

    /**
     * Transforms the selection of the dropdown to a text representation that
     * can be added to the text control of this combo.
     *
     * <p>
     * Note that by default the multiselect String is specified to show with
     * enclosing [] brackets and values separated by ", ". If you need to change
     * this you need to set the corresponding values in this NatCombo.
     *
     * @return String representation for the selection within the combo.
     */
    protected String getTransformedTextForSelection() {
        String result = ""; //$NON-NLS-1$
        String[] selection = getTransformedSelection();
        if (selection != null) {
            result = getTransformedText(selection);
        }
        return result;
    }

    /**
     * Transforms the given array of Strings to a text representation that can
     * be added to the text control of this combo.
     * <p>
     * If this NatCombo is only configured to support single selection, than
     * only the first value in the array will be processed. Otherwise the result
     * will be processed by concatenating the values.
     * <p>
     * Note that by default the multiselect String is specified to show with
     * enclosing [] brackets and values separated by ", ". If you need to change
     * this you need to set the corresponding values in this NatCombo.
     *
     * @param values
     *            The values to build the text representation from.
     * @return String representation for the selection within the combo.
     */
    protected String getTransformedText(String[] values) {
        String result = ""; //$NON-NLS-1$
        if (this.multiselect) {
            for (int i = 0; i < values.length; i++) {
                String selection = values[i];
                result += selection;
                if ((i + 1) < values.length) {
                    result += this.multiselectValueSeparator;
                }
            }
            result = this.multiselectTextPrefix + result
                    + this.multiselectTextSuffix;
        } else if (values.length > 0) {
            result = values[0];
        }
        return result;
    }

    /**
     * @param multiselectValueSeparator
     *            String that should be used to separate values in the String
     *            representation showed in the text control if multiselect is
     *            supported. <code>null</code> to use the default value
     *            separator.
     * @see NatCombo#DEFAULT_MULTI_SELECT_VALUE_SEPARATOR
     */
    public void setMultiselectValueSeparator(String multiselectValueSeparator) {
        if (multiselectValueSeparator == null) {
            this.multiselectValueSeparator = DEFAULT_MULTI_SELECT_VALUE_SEPARATOR;
        } else {
            this.multiselectValueSeparator = multiselectValueSeparator;
        }
    }

    /**
     * Set the prefix and suffix that will parenthesize the text that is created
     * out of the selected values if this NatCombo supports multiselection.
     *
     * @param multiselectTextPrefix
     *            String that should be used to prefix the generated String
     *            representation showed in the text control if multiselect is
     *            supported. <code>null</code> to use the default prefix.
     * @param multiselectTextSuffix
     *            String that should be used to suffix the generated String
     *            representation showed in the text control if multiselect is
     *            supported. <code>null</code> to use the default suffix.
     * @see NatCombo#DEFAULT_MULTI_SELECT_PREFIX
     * @see NatCombo#DEFAULT_MULTI_SELECT_SUFFIX
     */
    public void setMultiselectTextBracket(String multiselectTextPrefix,
            String multiselectTextSuffix) {
        if (multiselectTextPrefix == null) {
            this.multiselectTextPrefix = DEFAULT_MULTI_SELECT_PREFIX;
        } else {
            this.multiselectTextPrefix = multiselectTextPrefix;
        }

        if (multiselectTextSuffix == null) {
            this.multiselectTextSuffix = DEFAULT_MULTI_SELECT_SUFFIX;
        } else {
            this.multiselectTextSuffix = multiselectTextSuffix;
        }
    }

    /**
     * FocusListener that is used to ensure that the Text control and the
     * dropdown table control are sharing the same focus. If either of both
     * controls looses focus, the local focus flag is set to false and a delayed
     * background thread for focus lost is started. If the other control gains
     * focus, the local focus flag is set to true which skips the execution of
     * the delayed background thread. This means the NatCombo hasn't lost focus.
     *
     * @author Dirk Fauth
     *
     */
    class FocusListenerWrapper implements FocusListener {

        @Override
        public void focusLost(final FocusEvent e) {
            hasFocus = false;
            Display.getCurrent().timerExec(100, new Runnable() {
                @Override
                public void run() {
                    if (!hasFocus) {
                        focusLostRunnableActive = true;
                        for (FocusListener f : focusListener) {
                            f.focusLost(e);
                        }
                        focusLostRunnableActive = false;
                    }
                }
            });
        }

        @Override
        public void focusGained(FocusEvent e) {
            hasFocus = true;
            for (FocusListener f : focusListener) {
                f.focusGained(e);
            }
        }
    }

}
TOP

Related Classes of org.eclipse.nebula.widgets.nattable.widget.NatCombo$FocusListenerWrapper

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.