Package org.gwt.mosaic.ui.client

Source Code of org.gwt.mosaic.ui.client.ListBox$DataGrid

/*
* Copyright (c) 2008-2009 GWT Mosaic Georgios J. Georgopoulos.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package org.gwt.mosaic.ui.client;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import org.gwt.mosaic.core.client.DOM;
import org.gwt.mosaic.ui.client.ColumnWidget.ResizePolicy;
import org.gwt.mosaic.ui.client.layout.LayoutPanel;
import org.gwt.mosaic.ui.client.list.DefaultListModel;
import org.gwt.mosaic.ui.client.list.ListDataEvent;
import org.gwt.mosaic.ui.client.list.ListDataListener;
import org.gwt.mosaic.ui.client.list.ListModel;

import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.DeferredCommand;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.ui.ChangeListener;
import com.google.gwt.user.client.ui.ChangeListenerCollection;
import com.google.gwt.user.client.ui.FocusListener;
import com.google.gwt.user.client.ui.FocusListenerCollection;
import com.google.gwt.user.client.ui.HasFocus;
import com.google.gwt.user.client.ui.KeyboardListener;
import com.google.gwt.user.client.ui.KeyboardListenerCollection;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.user.client.ui.PopupPanel.PositionCallback;
import com.google.gwt.user.client.ui.impl.FocusImpl;
import com.google.gwt.widgetideas.table.client.FixedWidthFlexTable;
import com.google.gwt.widgetideas.table.client.FixedWidthGrid;
import com.google.gwt.widgetideas.table.client.SourceTableSelectionEvents;
import com.google.gwt.widgetideas.table.client.TableSelectionListener;
import com.google.gwt.widgetideas.table.client.SelectionGrid.SelectionPolicy;

/**
* This widget is used to create a list of items where one or more of the items
* may be selected. A {@code ListBox} may contain multiple columns. A separate
* model, {@link ListModel}, maintains the contents of the list.
*
* @author georgopoulos.georgios(at)gmail.com
*
* @param <T>
*/
public class ListBox<T> extends LayoutComposite implements HasFocus,
    ListDataListener {
  /**
   * The render used to set cell contents.
   *
   * @param <T>
   */
  public interface CellRenderer<T> {
    /**
     * Render the contents of a cell.
     *
     * @param listBox the {@code ListBox} that is asking the renderer to draw
     * @param row the row index
     * @param column the column index
     * @param item the item to render
     */
    void renderCell(ListBox<T> listBox, int row, int column, T item);
  }

  private static class DataGrid extends FixedWidthGrid {

    private Event onMouseDownEvent = null;

    private DoubleClickListenerCollection doubleClickListeners;

    private PopupMenu contextMenu;

    public DataGrid() {
      super();
      // sinkEvents(Event.MOUSEEVENTS | Event.ONCLICK | Event.KEYEVENTS);
    }

    public void addDoubleClickListener(DoubleClickListener listener) {
      if (doubleClickListeners == null) {
        doubleClickListeners = new DoubleClickListenerCollection();
        sinkEvents(Event.ONDBLCLICK);
      }
      doubleClickListeners.add(listener);
    }

    public PopupMenu getContextMenu() {
      return contextMenu;
    }

    @Override
    protected int getInputColumnWidth() {
      return super.getInputColumnWidth();
    }

    @Override
    protected void hoverCell(Element cellElem) {
      super.hoverCell(cellElem);
    }

    /**
     * @see com.google.gwt.widgetideas.table.client.overrides.HTMLTable
     */
    @Override
    public void onBrowserEvent(Event event) {
      Element targetRow = null;
      Element targetCell = null;

      switch (DOM.eventGetType(event)) {
        // Select a row on click
        case Event.ONMOUSEDOWN:
          onMouseDownEvent = event;
          super.onBrowserEvent(event);
          break;

        // Fire double click event
        case Event.ONDBLCLICK:
          doubleClickListeners.fireDblClick(this);
          break;

        // Show context menu
        case Event.ONCONTEXTMENU:
          targetCell = getEventTargetCell(event);
          if (targetCell == null) {
            return;
          }
          targetRow = DOM.getParent(targetCell);
          int targetRowIndex = getRowIndex(targetRow);
          if (!isRowSelected(targetRowIndex)) {
            super.onBrowserEvent(onMouseDownEvent);
          }
          DOM.eventPreventDefault(event);
          showContextMenu(event);
          break;

        default:
          super.onBrowserEvent(event);
      }
    }

    public void removeDoubleClickListener(DoubleClickListener listener) {
      if (doubleClickListeners != null) {
        doubleClickListeners.remove(listener);
      }
    }

    public void setContextMenu(PopupMenu contextMenu) {
      this.contextMenu = contextMenu;
      if (this.contextMenu != null) {
        sinkEvents(Event.ONCONTEXTMENU);
      }
    }

    private void showContextMenu(final Event event) {
      contextMenu.setPopupPositionAndShow(new PositionCallback() {
        public void setPosition(int offsetWidth, int offsetHeight) {
          contextMenu.setPopupPosition(event.getClientX(), event.getClientY());
        }
      });
    }

  }

  static final FocusImpl impl = FocusImpl.getFocusImplForPanel();

  private static final int INSERT_AT_END = -1;

  private final ColumnWidget columnWidget;
  private final DataGrid dataTable = new DataGrid();
  private final FixedWidthFlexTable headerTable;

  /**
   * The cell renderer used on the data table.
   */
  private CellRenderer<T> cellRenderer = new CellRenderer<T>() {
    public void renderCell(ListBox<T> listBox, int row, int column, T item) {
      if (item instanceof Widget) {
        listBox.setWidget(row, column, (Widget) item);
      } else {
        listBox.setText(row, column, item.toString());
      }
    }
  };

  /**
   * The values associated with each row.
   */
  private Map<Element, T> rowItems = new HashMap<Element, T>();

  private DoubleClickListenerCollection doubleClickListeners;

  private ChangeListenerCollection changeListeners;

  private FocusListenerCollection focusListeners;

  private KeyboardListenerCollection keyboardListeners;

  private ListModel<T> dataModel;

  /**
   * Creates an empty list box in single selection mode.
   */
  public ListBox() {
    this(null);
  }

  public ListBox(String[] columns) {
    super(impl.createFocusable());

    if (columns != null && columns.length > 0) {
      headerTable = new FixedWidthFlexTable();
      for (int column = 0; column < columns.length; ++column) {
        headerTable.setHTML(0, column, columns[column]);
      }
      setColumnsCount(columns.length);
    } else {
      headerTable = null;
    }

    final LayoutPanel layoutPanel = getLayoutPanel();
    columnWidget = new ColumnWidget(dataTable, headerTable) {
      @Override
      protected int getInputColumnWidth() {
        return dataTable.getInputColumnWidth();
      }

      @Override
      protected void hoverCell(Element cellElem) {
        dataTable.hoverCell(cellElem);
      }
    };
    setMultipleSelect(false);
   
    // Setup the scroll table
    columnWidget.setCellPadding(3);
    columnWidget.setCellSpacing(0);
    columnWidget.setResizePolicy(ResizePolicy.FILL_WIDTH);
   
    layoutPanel.add(columnWidget);

    // sinkEvents(Event.FOCUSEVENTS | Event.KEYEVENTS | Event.ONCLICK
    // | Event.MOUSEEVENTS | Event.ONMOUSEWHEEL);
    sinkEvents(Event.ONCLICK | Event.ONMOUSEOVER | Event.ONMOUSEOUT
        | Event.ONFOCUS | Event.ONKEYDOWN);

    // Hide focus outline in Mozilla/Webkit/Opera
    DOM.setStyleAttribute(getElement(), "outline", "0px");

    // Hide focus outline in IE 6/7
    DOM.setElementAttribute(getElement(), "hideFocus", "true");
  }

  /**
   *
   * @param listener
   */
  public void addChangeListener(ChangeListener listener) {
    if (changeListeners == null) {
      changeListeners = new ChangeListenerCollection();
      dataTable.addTableSelectionListener(new TableSelectionListener() {
        public void onAllRowsDeselected(SourceTableSelectionEvents sender) {
          //changeListeners.fireChange(ListBox.this);
        }

        public void onCellHover(SourceTableSelectionEvents sender, int row,
            int cell) {
          // Nothing to do here!
        }

        public void onCellUnhover(SourceTableSelectionEvents sender, int row,
            int cell) {
          // Nothing to do here!
        }

        public void onRowDeselected(SourceTableSelectionEvents sender, int row) {
          changeListeners.fireChange(ListBox.this);
        }

        public void onRowHover(SourceTableSelectionEvents sender, int row) {
          // Nothing to do here!
        }

        public void onRowsSelected(SourceTableSelectionEvents sender,
            int firstRow, int numRows) {
          changeListeners.fireChange(ListBox.this);
        }

        public void onRowUnhover(SourceTableSelectionEvents sender, int row) {
          // Nothing to do here!
        }
      });
    }
    changeListeners.add(listener);
  }

  public void addDoubleClickListener(DoubleClickListener listener) {
    if (doubleClickListeners == null) {
      doubleClickListeners = new DoubleClickListenerCollection();
      dataTable.addDoubleClickListener(new DoubleClickListener() {
        public void onDoubleClick(Widget sender) {
          doubleClickListeners.fireDblClick(ListBox.this);
        }
      });
    }
    doubleClickListeners.add(listener);
  }

  public void addFocusListener(FocusListener listener) {
    if (focusListeners == null) {
      focusListeners = new FocusListenerCollection();
    }
    focusListeners.add(listener);
  }

  public void addKeyboardListener(KeyboardListener listener) {
    if (keyboardListeners == null) {
      keyboardListeners = new KeyboardListenerCollection();
    }
    keyboardListeners.add(listener);
  }

  private void checkIndex(int index) {
    if (index < 0 || index >= getItemCount()) {
      throw new IndexOutOfBoundsException();
    }
  }

  /*
   * (non-Javadoc)
   *
   * @see
   * org.gwt.mosaic.ui.client.list.ListDataListener#contentsChanged(org.gwt.
   * mosaic.ui.client.list.ListDataEvent)
   */
  public void contentsChanged(ListDataEvent event) {
    if (dataModel == event.getSource()) {
      for (int i = event.getIndex0(), n = event.getIndex1(); i <= n; ++i) {
        if (i < getItemCount()) {
          renderItemOnUpdate(i, dataModel.getElementAt(i));
        }
        // } else {
        // renderItemOnInsert(dataModel.getElementAt(i), INSERT_AT_END);
        // }
      }
    }
  }

  private void eatEvent(Event event) {
    DOM.eventCancelBubble(event, true);
    DOM.eventPreventDefault(event);
  }

  /**
   * Get the {@link CellRenderer} used to render cells.
   *
   * @return the current renderer
   */
  public CellRenderer<T> getCellRenderer() {
    return cellRenderer;
  }

  /**
   * Gets the number of columns in this grid.
   *
   * @return the number of columns
   */
  public int getColumnsCount() {
    return dataTable.getColumnCount();
  }

  public PopupMenu getContextMenu() {
    return dataTable.getContextMenu();
  }

  /**
   * Gets the item at the specified index.
   *
   * @param index the index of the item to be retrieved
   * @return the item
   * @throws IndexOutOfBoundsException if the index is out of range
   */
  public T getItem(int index) {
    checkIndex(index);
    return rowItems.get(dataTable.getRowFormatter().getElement(index));
  }

  /**
   * Gets the number of items present in the list box.
   *
   * @return the number of items
   */
  public int getItemCount() {
    return dataTable.getRowCount();
  }

  /**
   * Returns the data model.
   *
   * @return the {@code ListModel} that provides the displayed list of items
   */
  public ListModel<T> getModel() {
    if (dataModel == null) {
      setModel(new DefaultListModel<T>());
    }
    return dataModel;
  }

  /**
   * Gets the currently-selected item. If multiple items are selected, this
   * method will returns the first selected item ({@link #isItemSelected(int)}
   * can be used to query individual items).
   *
   * @return the selected index, or {@code -1} if none is selected
   * @see #isItemSelected(int)
   * @see #addChangeListener(ChangeListener)
   */
  public int getSelectedIndex() {
    Set<Integer> selection = dataTable.getSelectedRows();
    if (selection != null && selection.size() > 0) {
      return selection.iterator().next();
    }
    return -1;
  }

  /**
   * Returns a {@code Set} of all the selected indices.
   *
   * @return all of the selected indices in a {@code Set}
   * @see #addChangeListener(ChangeListener)
   */
  public Set<Integer> getSelectedIndices() {
    return dataTable.getSelectedRows();
  }

  public int getTabIndex() {
    return impl.getTabIndex(getElement());
  }

  /*
   * (non-Javadoc)
   *
   * @see
   * org.gwt.mosaic.ui.client.list.ListDataListener#intervalAdded(org.gwt.mosaic
   * .ui.client.list.ListDataEvent)
   */
  public void intervalAdded(ListDataEvent event) {
    if (dataModel == event.getSource()) {
      for (int i = event.getIndex0(), n = event.getIndex1(); i <= n; ++i) {
        if (i < getItemCount()) {
          renderItemOnInsert(dataModel.getElementAt(i), i);
        } else {
          renderItemOnInsert(dataModel.getElementAt(i), INSERT_AT_END);
        }
      }
    }
  }

  /*
   * (non-Javadoc)
   *
   * @see
   * org.gwt.mosaic.ui.client.list.ListDataListener#intervalRemoved(org.gwt.
   * mosaic.ui.client.list.ListDataEvent)
   */
  public void intervalRemoved(ListDataEvent event) {
    if (dataModel == event.getSource()) {
      for (int i = event.getIndex1(), n = event.getIndex0(); i >= n; --i) {
        // for (int i = event.getIndex0(), n = event.getIndex1(); i <= n; ++i) {
        // for (int i = event.getIndex0(), n = event.getIndex1(); i < n; ++i) {
        renderOnRemove(i);
      }
    }
  }

  /**
   * Determines whether as individual list is selected.
   *
   * @param index the index of the item to be tested
   * @return {@code true} if the item is selected
   * @throws IndexOutOfBoundsException if the index is out of range
   * @see #getSelectedIndices()
   * @see #addChangeListener(ChangeListener)
   */
  public boolean isItemSelected(int index) {
    checkIndex(index);
    return dataTable.isRowSelected(index);
  }

  /**
   * Gets whether this list allows multiple selection.
   *
   * @return {@code true} if multiple selection is allowed
   * @see #setMultipleSelect(boolean)
   */
  public boolean isMultipleSelect() {
    return dataTable.getSelectionPolicy() == SelectionPolicy.MULTI_ROW;
  }

  @Override
  public void layout() {
    DeferredCommand.addCommand(new Command() {
      public void execute() {
        columnWidget.fillWidth();
      }
    });
    super.layout();
  }

  private void moveDown() {
    if (selectFirstItemIfNodeSelected()) {
      return;
    }
    selectNextItem();
  }

  private void moveUp() {
    if (selectFirstItemIfNodeSelected()) {
      return;
    }
    selectPrevItemItem();
  }

  /**
   * {@inheritDoc}
   *
   * @see com.google.gwt.widgetideas.table.client.overrides.HTMLTable
   */
  @Override
  public void onBrowserEvent(Event event) {
    switch (DOM.eventGetType(event)) {
      case Event.ONCLICK:
        setFocus(true);
        super.onBrowserEvent(event);
        break;
      case Event.ONKEYDOWN:
        int keyCode = DOM.eventGetKeyCode(event);
        switch (keyCode) {
          case KeyboardListener.KEY_UP:
            moveUp();
            eatEvent(event);
            break;
          case KeyboardListener.KEY_DOWN:
            moveDown();
            eatEvent(event);
            break;
          case KeyboardListener.KEY_LEFT:
            DOM.scrollIntoView((Element) dataTable.getRowFormatter().getElement(
                getSelectedIndex()).getFirstChild());
            break;
          case KeyboardListener.KEY_RIGHT:
            DOM.scrollIntoView((Element) dataTable.getRowFormatter().getElement(
                getSelectedIndex()).getLastChild());
            break;
          default:
            super.onBrowserEvent(event);
            break;
        }
        break;

      default:
        super.onBrowserEvent(event);
    }
  }

  /**
   *
   * @param listener
   */
  public void removeChangeListener(ChangeListener listener) {
    if (changeListeners != null) {
      changeListeners.remove(listener);
    }
  }

  public void removeDoubleClickListener(DoubleClickListener listener) {
    if (doubleClickListeners != null) {
      doubleClickListeners.remove(listener);
    }
  }

  public void removeFocusListener(FocusListener listener) {
    if (focusListeners != null) {
      focusListeners.remove(listener);
    }
  }

  public void removeKeyboardListener(KeyboardListener listener) {
    if (keyboardListeners != null) {
      keyboardListeners.remove(listener);
    }
  }

  /**
   * Renders an inserted item.
   *
   * @param item the item to be inserted
   * @param index the index at which to insert it
   */
  protected void renderItemOnInsert(T item, int index) {
    if (dataTable.getColumnCount() == 0) {
      dataTable.resizeColumns(1);
    }
    if ((index == INSERT_AT_END) || (index == dataTable.getRowCount())) {
      // Insert the new row
      dataTable.insertRow(index = dataTable.getRowCount());
    } else {
      // Insert the new row
      dataTable.insertRow(index);
    }
    // Set the data in the new row
    for (int cellIndex = 0, n = dataTable.getColumnCount(); cellIndex < n; ++cellIndex) {
      cellRenderer.renderCell(this, index, cellIndex, item);
    }
    // Map item with <tr>
    final Element tr = dataTable.getRowFormatter().getElement(index);
    rowItems.put(tr, item);
  }

  /**
   * Renders an updated item.
   *
   * @param index the index of the item to be set
   * @param item the item's new value
   * @throws IndexOutOfBoundsException if the index is out of range
   */
  protected void renderItemOnUpdate(int index, T item) {
    checkIndex(index);
    if (item == null) {
      throw new NullPointerException("Cannot set an item to null");
    }

    // Set the data in the row
    for (int cellIndex = 0, n = dataTable.getColumnCount(); cellIndex < n; ++cellIndex) {
      cellRenderer.renderCell(this, index, cellIndex, item);
    }

    // Map the new item with <tr>
    rowItems.put(dataTable.getRowFormatter().getElement(index), item);
  }

  /**
   * Removes all items from the list box.
   */
  public void renderOnClear() {
    dataTable.resizeRows(0);
    rowItems.clear();
  }

  /**
   * Removes the item at the specified index.
   *
   * @param index the index of the item to be removed
   * @throws IndexOutOfBoundsException if the index is out of range
   */
  public void renderOnRemove(int index) {
    checkIndex(index);
    rowItems.remove(getItem(index));
    dataTable.removeRow(index);
  }

  /**
   * Selects the firs item in the list if no items are currently selected. This
   * method assumes that the list has at least 1 item.
   *
   * @return {@code true} if no item was previosly selected and the first item
   *         in the list was selected, {@code false} otherwise
   */
  private boolean selectFirstItemIfNodeSelected() {
    if (getSelectedIndex() == -1) {
      setSelectedIndex(0);
      return true;
    }
    return false;
  }

  private void selectNextItem() {
    int index = getSelectedIndex();
    if (index == -1) {
      return;
    }

    if (index < getItemCount() - 1) {
      setSelectedIndex(++index);
    } else {
      // we're at the end, loop around to the start
      setSelectedIndex(0);
    }

    DOM.scrollIntoView((Element) dataTable.getRowFormatter().getElement(
        getSelectedIndex()).getFirstChild());
  }

  private void selectPrevItemItem() {
    int index = getSelectedIndex();
    if (index == -1) {
      return;
    }

    if (index > 0) {
      setSelectedIndex(--index);
    } else {
      // we're at the start, loop around to the end
      setSelectedIndex(getItemCount() - 1);
    }

    DOM.scrollIntoView((Element) dataTable.getRowFormatter().getElement(
        getSelectedIndex()).getFirstChild());
  }

  public void setAccessKey(char key) {
    impl.setAccessKey(getElement(), key);
  }

  /**
   * Set the {@link CellRenderer} used to render cell contents.
   *
   * @param cellRenderer the new renderer
   */
  public void setCellRenderer(CellRenderer<T> cellRenderer) {
    this.cellRenderer = cellRenderer;
  }

  /**
   * Resizes the {@code ListBox} to the specified number of columns.
   *
   * @param columns the number of columns
   * @throws IndexOutOfBoundsException
   */
  public void setColumnsCount(int columns) {
    dataTable.resizeColumns(columns);
  }

  public void setContextMenu(PopupMenu contextMenu) {
    dataTable.setContextMenu(contextMenu);
  }

  public void setFocus(boolean focused) {
    if (focused) {
      impl.focus(getElement());
    } else {
      impl.blur(getElement());
    }
  }

  public void setHTML(int row, int column, String html) {
    dataTable.setHTML(row, column, html);
  }

  /**
   * Sets whether an individual list item is selected.
   * <p>
   * Note that setting the selection programmatically does <em>not</em> cause
   * the {@link ChangeListener#onChange(Widget)} event to be fired.
   *
   * @param index the index of the item to be selected or unselected
   * @param selected {@code true} to select the item
   * @throws IndexOutOfBoundsException if the index is out of range
   */
  public void setItemSelected(int index, boolean selected) {
    checkIndex(index);
    dataTable.selectRow(index, false);
  }

  /**
   * Sets the model that represents the contents of the {@code ListBox}, and
   * then clears the list's selection.
   *
   * @param dataModel the {@link ListModel} that provides the list of items for
   *          display
   * @see #getModel
   */
  public void setModel(ListModel<T> dataModel) {
    if (dataModel == null) {
      throw new IllegalArgumentException("model must be non null");
    }
    if (this.dataModel == dataModel) {
      return;
    }
    if (this.dataModel != null) {
      this.dataModel.removeListDataListener(this);
      dataTable.deselectAllRows();
      renderOnClear();
    }

    this.dataModel = dataModel;

    // TODO bulk update
    for (int i = 0, n = dataModel.getSize(); i < n; ++i) {
      renderItemOnInsert(dataModel.getElementAt(i), INSERT_AT_END);
    }

    this.dataModel.addListDataListener(this);

    layout();
  }

  /**
   * Sets whether this list allows multiple selections.
   *
   * @param multiple {@code true} to allow multiple selections
   */
  public void setMultipleSelect(boolean multiple) {
    dataTable.setSelectionPolicy(multiple ? SelectionPolicy.MULTI_ROW
        : SelectionPolicy.ONE_ROW);
  }

  /**
   * Sets the currently selected index.
   * <p>
   * After calling this method, only the specified item in the list will remain
   * selected. For a {@code ListBox} with multiple selection enabled, see
   * {@link #setItemSelected(int, boolean)} to select multiple items at a time.
   * <p>
   * TODO (check) Note that setting the selected index programmatically does
   * <em>not</em> cause the {@link ChangeListener#onChange(Widget)} event to be
   * fired.
   *
   * @param index the index of the item to be selected
   * @see #setItemSelected(int, boolean)
   * @see #getSelectedIndex()
   */
  public void setSelectedIndex(int index) {
    checkIndex(index);
    dataTable.selectRow(index, true);
  }

  public void setTabIndex(int index) {
    impl.setTabIndex(getElement(), index);
  }

  public void setText(int row, int column, String text) {
    dataTable.setText(row, column, text);
  }

  public void setWidget(int row, int column, Widget widget) {
    dataTable.setWidget(row, column, widget);
  }

}
TOP

Related Classes of org.gwt.mosaic.ui.client.ListBox$DataGrid

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.