<commit_before>package org.pentaho.ui.xul.gwt.tags;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Vector;

import org.pentaho.gwt.widgets.client.buttons.ImageButton;
import org.pentaho.gwt.widgets.client.listbox.CustomListBox;
import org.pentaho.gwt.widgets.client.table.BaseTable;
import org.pentaho.gwt.widgets.client.table.ColumnComparators.BaseColumnComparator;
import org.pentaho.gwt.widgets.client.table.TableColumnSortListener;
import org.pentaho.gwt.widgets.client.ui.Draggable;
import org.pentaho.gwt.widgets.client.utils.string.StringUtils;
import org.pentaho.ui.xul.XulComponent;
import org.pentaho.ui.xul.XulDomContainer;
import org.pentaho.ui.xul.XulEventSource;
import org.pentaho.ui.xul.XulException;
import org.pentaho.ui.xul.binding.Binding;
import org.pentaho.ui.xul.binding.BindingConvertor;
import org.pentaho.ui.xul.binding.InlineBindingExpression;
import org.pentaho.ui.xul.components.XulTreeCell;
import org.pentaho.ui.xul.components.XulTreeCol;
import org.pentaho.ui.xul.containers.XulRoot;
import org.pentaho.ui.xul.containers.XulTree;
import org.pentaho.ui.xul.containers.XulTreeChildren;
import org.pentaho.ui.xul.containers.XulTreeCols;
import org.pentaho.ui.xul.containers.XulTreeItem;
import org.pentaho.ui.xul.containers.XulTreeRow;
import org.pentaho.ui.xul.dnd.DataTransfer;
import org.pentaho.ui.xul.dnd.DropEvent;
import org.pentaho.ui.xul.dnd.DropPosition;
import org.pentaho.ui.xul.dom.Document;
import org.pentaho.ui.xul.dom.Element;
import org.pentaho.ui.xul.gwt.AbstractGwtXulContainer;
import org.pentaho.ui.xul.gwt.GwtXulHandler;
import org.pentaho.ui.xul.gwt.GwtXulParser;
import org.pentaho.ui.xul.gwt.binding.GwtBinding;
import org.pentaho.ui.xul.gwt.binding.GwtBindingContext;
import org.pentaho.ui.xul.gwt.binding.GwtBindingMethod;
import org.pentaho.ui.xul.gwt.tags.util.TreeItemWidget;
import org.pentaho.ui.xul.gwt.util.Resizable;
import org.pentaho.ui.xul.gwt.util.XulDragController;
import org.pentaho.ui.xul.impl.XulEventHandler;
import org.pentaho.ui.xul.stereotype.Bindable;
import org.pentaho.ui.xul.util.TreeCellEditor;
import org.pentaho.ui.xul.util.TreeCellEditorCallback;
import org.pentaho.ui.xul.util.TreeCellRenderer;

import com.allen_sauer.gwt.dnd.client.DragContext;
import com.allen_sauer.gwt.dnd.client.VetoDragException;
import com.allen_sauer.gwt.dnd.client.drop.AbstractPositioningDropController;
import com.google.gwt.core.client.GWT;
import com.google.gwt.gen2.table.client.SelectionGrid;
import com.google.gwt.gen2.table.event.client.RowSelectionEvent;
import com.google.gwt.gen2.table.event.client.RowSelectionHandler;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.ChangeListener;
import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.KeyboardListener;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.Tree;
import com.google.gwt.user.client.ui.TreeItem;
import com.google.gwt.user.client.ui.TreeListener;
import com.google.gwt.user.client.ui.Widget;

public class GwtTree extends AbstractGwtXulContainer implements XulTree, Resizable, TableColumnSortListener {

  /**
   * Cached elements.
   */
  private Collection elements;
  private GwtBindingMethod dropVetoerMethod;
  private XulEventHandler dropVetoerController;

  public static void register() {
    GwtXulParser.registerHandler("tree", new GwtXulHandler() {
      public Element newInstance() {
        return new GwtTree();
      }
    });
  }

  XulTreeCols columns = null;
  XulTreeChildren rootChildren = null;
  private XulDomContainer domContainer;
  private Tree tree;

  private boolean suppressEvents = false;
  private boolean editable = false;
  private boolean visible = true;
  private boolean isSortable = false;
  private String command;

  private Map<String, TreeCellEditor> customEditors = new HashMap<String, TreeCellEditor>();
  private Map<String, TreeCellRenderer> customRenderers = new HashMap<String, TreeCellRenderer>();

  private List<Binding> elementBindings = new ArrayList<Binding>();
  private List<Binding> expandBindings = new ArrayList<Binding>();
  private boolean suppressExpansionScrollEvents;
  private DropPositionIndicator dropPosIndicator = new DropPositionIndicator();

  private boolean showAllEditControls = true;

  private boolean dropIconsVisible = true;
  private String onedit, onselect, seltype;

  @Bindable
  public boolean isVisible() {
    return visible;
  }

  @Bindable
  public void setVisible(boolean visible) {
    this.visible = visible;
    if (simplePanel != null) {
      simplePanel.setVisible(visible);
    }
  }

  /**
   * @return the tree managed by this GwtTree
   */
  public Tree getTree() {
    return tree;
  }

  /**
   * Used when this widget is a tree. Not used when this widget is a table.
   */
  private FlowPanel scrollPanel = new FlowPanel();
  {
    scrollPanel.setStylePrimaryName("tree-scroll-panel");
  }

  /**
   * The managed object. If this widget is a tree, then the tree is added to the scrollPanel, which is added to this simplePanel. If this widget is a table,
   * then the table is added directly to this simplePanel.
   */
  private SimplePanel simplePanel = new SimplePanel();

  /**
   * Clears the parent panel and adds the given widget.
   * 
   * @param widget
   *          tree or table to set in parent panel
   */
  protected void setWidgetInPanel(final Widget widget) {
    if (isHierarchical()) {
      scrollPanel.clear();
      simplePanel.add(scrollPanel);
      scrollPanel.add(widget);
      scrollPanel.add(dropPosIndicator);
    } else {
      simplePanel.clear();
      simplePanel.add(widget);
    }
  }

  public GwtTree() {
    super("tree");

    // managedObject is neither a native GWT tree nor a table since the entire native GWT object is thrown away each
    // time we call setup{Tree|Table}; because the widget is thrown away, we need to reconnect the new widget to the
    // simplePanel, which is the managedObject
    setManagedObject(simplePanel);

  }

  public void init(com.google.gwt.xml.client.Element srcEle, XulDomContainer container) {
    super.init(srcEle, container);
    setOnselect(srcEle.getAttribute("onselect"));
    setOnedit(srcEle.getAttribute("onedit"));
    setSeltype(srcEle.getAttribute("seltype"));

    if (StringUtils.isEmpty(srcEle.getAttribute("pen:ondrop")) == false) {
      setOndrop(srcEle.getAttribute("pen:ondrop"));
    }
    if (StringUtils.isEmpty(srcEle.getAttribute("pen:ondrag")) == false) {
      setOndrag(srcEle.getAttribute("pen:ondrag"));
    }
    if (StringUtils.isEmpty(srcEle.getAttribute("pen:drageffect")) == false) {
      setDrageffect(srcEle.getAttribute("pen:drageffect"));
    }

    setDropvetoer(srcEle.getAttribute("pen:dropvetoer"));
    if (StringUtils.isEmpty(srcEle.getAttribute("pen:showalleditcontrols")) == false) {
      this.setShowalleditcontrols(srcEle.getAttribute("pen:showalleditcontrols").equals("true"));
    }
    this.setEditable("true".equals(srcEle.getAttribute("editable")));

    if (StringUtils.isEmpty(srcEle.getAttribute("pen:dropiconsvisible")) == false) {
      this.setDropIconsVisible(srcEle.getAttribute("pen:dropiconsvisible").equals("true"));
    }

    this.domContainer = container;
  }

  private List<TreeItemDropController> dropHandlers = new ArrayList<TreeItemDropController>();

  public void addChild(Element element) {
    super.addChild(element);
    if (element.getName().equals("treecols")) {
      columns = (XulTreeCols) element;
    } else if (element.getName().equals("treechildren")) {
      rootChildren = (XulTreeChildren) element;
    }
  }

  public void addTreeRow(XulTreeRow row) {
    GwtTreeItem item = new GwtTreeItem();
    item.setRow(row);
    this.rootChildren.addItem(item);

    // update UI
    updateUI();
  }

  private BaseTable table;
  private boolean isHierarchical = false;
  private boolean firstLayout = true;
  private Object[][] currentData;

  // need to handle layouting
  public void layout() {
    if (this.getRootChildren() == null) {
      // most likely an overlay
      return;
    }
    if (firstLayout) {

      XulTreeItem item = (XulTreeItem) this.getRootChildren().getFirstChild();

      if (item != null && item.getAttributeValue("container") != null && item.getAttributeValue("container").equals("true")) {
        isHierarchical = true;
      }
      firstLayout = false;
    }

    if (isHierarchical()) {
      setupTree();
    } else {
      setupTable();
    }
    setVisible(visible);
  }

  private int prevSelectionPos = -1;

  private void setupTree() {
    if (tree == null) {
      tree = new Tree();
      setWidgetInPanel(tree);
    }
    if (getWidth() > 0) {
      scrollPanel.setWidth(getWidth() + "px"); //$NON-NLS-1$
    }
    if (getHeight() > 0) {
      scrollPanel.setHeight(getHeight() + "px"); //$NON-NLS-1$
    }

    tree.addTreeListener(new TreeListener() {

      public void onTreeItemSelected(TreeItem arg0) {

        String jscmd = ((GwtTreeItem) ((TreeItemWidget) arg0.getWidget()).getTreeItem()).getJsCommand();
        if (!StringUtils.isEmpty(jscmd)) {
          executeJS(jscmd);
        } else {
          String cmd = ((GwtTreeItem) ((TreeItemWidget) arg0.getWidget()).getTreeItem()).getCommand();
          if (!StringUtils.isEmpty(cmd)) {
            invoke(cmd);
          }
        }

        int pos = -1;
        int curPos = 0;
        for (int i = 0; i < tree.getItemCount(); i++) {
          TreeItem tItem = tree.getItem(i);
          TreeCursor cursor = GwtTree.this.findPosition(tItem, arg0, curPos);
          pos = cursor.foundPosition;
          curPos = cursor.curPos + 1;
          if (pos > -1) {
            break;
          }
        }

        if (pos > -1 && GwtTree.this.suppressEvents == false && prevSelectionPos != pos) {
          GwtTree.this.changeSupport.firePropertyChange("selectedRows", null, new int[] { pos });
          GwtTree.this.changeSupport.firePropertyChange("absoluteSelectedRows", null, new int[] { pos });
          GwtTree.this.fireSelectedItems();
        }
        prevSelectionPos = pos;

      }

      public void onTreeItemStateChanged(TreeItem item) {
        ((TreeItemWidget) item.getWidget()).getTreeItem().setExpanded(item.getState());
      }

    });
    updateUI();
  }

  private class TreeCursor {
    int foundPosition = -1;
    int curPos;
    TreeItem itemToFind;

    public TreeCursor(int curPos, TreeItem itemToFind, int foundPosition) {
      this.foundPosition = foundPosition;
      this.curPos = curPos;
      this.itemToFind = itemToFind;
    }
  }

  private TreeCursor findPosition(TreeItem curItem, TreeItem itemToFind, int curPos) {
    if (curItem == itemToFind) {
      TreeCursor p = new TreeCursor(curPos, itemToFind, curPos);
      return p;
      // } else if(curItem.getChildIndex(itemToFind) > -1) {
      // curPos = curPos+1;
      // return new TreeCursor(curPos+curItem.getChildIndex(itemToFind) , itemToFind, curPos+curItem.getChildIndex(itemToFind));
    } else {
      for (int i = 1; i - 1 < curItem.getChildCount(); i++) {
        TreeCursor p = findPosition(curItem.getChild(i - 1), itemToFind, curPos + 1);
        curPos = p.curPos;
        if (p.foundPosition > -1) {
          return p;
        }
      }
      // curPos += curItem.getChildCount() ;
      return new TreeCursor(curPos, itemToFind, -1);
    }

  }

  private void populateTree() {
    tree.removeItems();
    TreeItem topNode = new TreeItem("placeholder");
    if (this.rootChildren == null) {
      this.rootChildren = (XulTreeChildren) this.children.get(1);
    }
    for (XulComponent c : this.rootChildren.getChildNodes()) {
      XulTreeItem item = (XulTreeItem) c;
      TreeItem node = createNode(item);
      tree.addItem(node);
    }

    if (StringUtils.isEmpty(this.ondrag) == false) {
      for (int i = 0; i < tree.getItemCount(); i++) {
        madeDraggable(tree.getItem(i));
      }
    }

  }

  private void madeDraggable(TreeItem item) {

    XulDragController.getInstance().makeDraggable(item.getWidget());

    for (int i = 0; i < item.getChildCount(); i++) {
      madeDraggable(item.getChild(i));
    }
  }

  private List<XulComponent> colCollection = new ArrayList<XulComponent>();

  private void populateTable() {

    int rowCount = getRootChildren().getItemCount();
    // Getting column editors ends up calling getChildNodes several times. we're going to cache the column collections
    // for the duration of the operation then clear it out.
    colCollection = getColumns().getChildNodes();
    int colCount = colCollection.size();

    currentData = new Object[rowCount][colCount];

    for (int i = 0; i < rowCount; i++) {
      for (int j = 0; j < colCount; j++) {
        currentData[i][j] = getColumnEditor(j, i);
      }
    }

    table.populateTable(currentData, elements);
    int totalFlex = 0;
    boolean allFlexing = true;
    for (int i = 0; i < colCount; i++) {
      XulTreeCol col = (XulTreeCol) colCollection.get(i);
      // Get the direction of the sort
      String sortDirection = col.getSortDirection();
      // if any column has sortActive flag on, then we will enable the sorting on a table
      // Also we will sort that column by default with a provided direction.
      if (col.isSortActive()) {
        isSortable = true;
        table.setSortingEnabled(true);
        table.sortColumn(i, sortDirection != null && sortDirection.equals("ASCENDING") ? true : false); //$NON-NLS-1$
      }
      int fx = colCollection.get(i).getFlex();
      totalFlex += fx;
      if (fx == 0) {
        allFlexing = false;
      }
    }
    // If the table is sortable then set all columns to be sortable. This enables the sorting by clicking the column header
    if (isSortable) {
      for (int i = 0; i < colCount; i++) {
        table.setColumnSortable(i, true);
      }
    }
    if (totalFlex > 0) {
      table.fillWidth();
    } else {
      table.noFill();
    }
    // If all flexing need to hide horizontal scrolling to fix an IE7 issue
    if (allFlexing) {
      table.suppressHorizontalScrolling();
    }
    colCollection = new ArrayList<XulComponent>();

    if (this.selectedRows != null && this.selectedRows.length > 0) {
      for (int i = 0; i < this.selectedRows.length; i++) {
        int idx = this.selectedRows[i];
        if (idx > -1 && idx < currentData.length) {
          table.selectRow(idx);
        }
      }
    }
  }

  private TreeItem createNode(final XulTreeItem item) {
    TreeItem node = new TreeItem("empty") {
      @Override
      public void setSelected(boolean selected) {
        super.setSelected(selected);
        if (selected) {
          this.getWidget().addStyleDependentName("selected");
        } else {
          this.getWidget().removeStyleDependentName("selected");
        }
      }
    };
    item.setManagedObject(node);
    if (item == null || item.getRow() == null || item.getRow().getChildNodes().size() == 0) {
      return node;
    }
    TreeItemWidget tempWidget = new TreeItemWidget(item);
    tempWidget.setDropIconsVisible(isDropIconsVisible());
    final TreeItemWidget tWidget = tempWidget;

    final String spacerPath = "images/spacer.gif";

    PropertyChangeListener listener = new PropertyChangeListener() {
      public void propertyChange(PropertyChangeEvent evt) {
        if (evt.getPropertyName().equals("image") && item.getImage() != null) {
          Image img = new Image(GWT.getModuleBaseURL() + item.getImage());
          if (item.getClassname() != null) {
            img.setUrl(GWT.getModuleBaseURL() + spacerPath);
          }
          tWidget.setImage(img);
        } else if (evt.getPropertyName().equals("label")) {
          tWidget.setLabel(item.getRow().getCell(0).getLabel());
        } else if (evt.getPropertyName().equals("classname") && item.getImage() != null) {
          tWidget.getImage().removeStyleName((String) evt.getOldValue());
          tWidget.getImage().addStyleName(item.getClassname());
        }
      }
    };

    ((GwtTreeItem) item).addPropertyChangeListener("image", listener);
    ((GwtTreeItem) item).addPropertyChangeListener("classname", listener);
    ((GwtTreeCell) item.getRow().getCell(0)).addPropertyChangeListener("label", listener);

    if (item.getImage() != null) {
      Image img = new Image(GWT.getModuleBaseURL() + item.getImage());

      if (item.getClassname() != null) {
        img.addStyleName(item.getClassname());
        img.setUrl(GWT.getModuleBaseURL() + spacerPath);
      }

      tWidget.setImage(img);
    }



    tWidget.setLabel(item.getRow().getCell(0).getLabel());
    if (this.ondrop != null) {
      TreeItemDropController controller = new TreeItemDropController(item, tWidget, this);
      XulDragController.getInstance().registerDropController(controller);
      this.dropHandlers.add(controller);
    }

    node.setWidget(tWidget);

    if (item.getChildNodes().size() > 1) {
      // has children
      // TODO: make this more defensive
      XulTreeChildren children = (XulTreeChildren) item.getChildNodes().get(1);
      for (XulComponent c : children.getChildNodes()) {

        TreeItem childNode = createNode((XulTreeItem) c);
        node.addItem(childNode);
      }
    }
    return node;
  }

  private String extractDynamicColType(Object row, int columnPos) {
    GwtBindingMethod method = GwtBindingContext.typeController.findGetMethod(row, this.columns.getColumn(columnPos).getColumntypebinding());
    try {
      return (String) method.invoke(row, new Object[] {});
    } catch (Exception e) {
      System.out.println("Could not extract column type from binding");
    }
    return "text"; // default //$NON-NLS-1$
  }

  private Binding createBinding(XulEventSource source, String prop1, XulEventSource target, String prop2) {
    if (bindingProvider != null) {
      return bindingProvider.getBinding(source, prop1, target, prop2);
    }
    return new GwtBinding(source, prop1, target, prop2);
  }

  private Widget getColumnEditor(final int x, final int y) {

    final XulTreeCol column = (XulTreeCol) colCollection.get(x);
    String colType = ((XulTreeCol) colCollection.get(x)).getType();
    XulTreeItem item = getRootChildren().getItem(y);
    if(item == null){
      return new HTML("");
    }
    XulTreeRow row1 = item.getRow();
    if(row1 == null){
      return new HTML("");
    }
    final GwtTreeCell cell = (GwtTreeCell) (row1.getChildNodes().size() > x ? row1.getCell(x) : null);
    if(cell == null){
      return new HTML("");
    }
    String val = cell.getLabel();

    // If collection bound, bindings may need to be updated for runtime changes in column types.
    if (this.elements != null) {
      try {
        this.addBindings(column, cell, this.elements.toArray()[y]);
      } catch (InvocationTargetException e) {
        e.printStackTrace();
      } catch (XulException e) {
        e.printStackTrace();
      }
    }

    if (StringUtils.isEmpty(colType) == false && colType.equalsIgnoreCase("dynamic")) {
      Object row = elements.toArray()[y];
      colType = extractDynamicColType(row, x);
    }

    int[] selectedRows = getSelectedRows();

    if ((StringUtils.isEmpty(colType) || !column.isEditable())
        || ((showAllEditControls == false && (selectedRows.length != 1 || selectedRows[0] != y)) && !colType.equals("checkbox"))) {
      if (colType != null && (colType.equalsIgnoreCase("combobox") || colType.equalsIgnoreCase("editablecombobox"))) {
        Vector vals = (Vector) cell.getValue();
        int idx = cell.getSelectedIndex();
        if (colType.equalsIgnoreCase("editablecombobox")) {
          return new HTML(cell.getLabel());
        } else if (idx < vals.size()) {
          return new HTML("" + vals.get(idx));
        } else {
          return new HTML("");
        }
      } else {
        return new HTML(val);
      }
    } else if (colType.equalsIgnoreCase("text")) {

      try {

        GwtTextbox b = (GwtTextbox) this.domContainer.getDocumentRoot().createElement("textbox");
        b.setDisabled(!column.isEditable());
        b.layout();
        b.setValue(val);
        Binding bind = createBinding(cell, "label", b, "value");
        bind.setBindingType(Binding.Type.BI_DIRECTIONAL);
        domContainer.addBinding(bind);
        bind.fireSourceChanged();

        bind = createBinding(cell, "disabled", b, "disabled");
        bind.setBindingType(Binding.Type.BI_DIRECTIONAL);
        domContainer.addBinding(bind);
        bind.fireSourceChanged();

        return (Widget) b.getManagedObject();
      } catch (Exception e) {
        System.out.println("error creating textbox, fallback");
        e.printStackTrace();
        final TextBox b = new TextBox();
        b.addKeyboardListener(new KeyboardListener() {

          public void onKeyDown(Widget arg0, char arg1, int arg2) {
          }

          public void onKeyPress(Widget arg0, char arg1, int arg2) {
          }

          public void onKeyUp(Widget arg0, char arg1, int arg2) {
            getRootChildren().getItem(y).getRow().getCell(x).setLabel(b.getText());
          }

        });
        b.setText(val);
        return b;
      }
    } else if (colType.equalsIgnoreCase("checkbox")) {
      try {
        GwtCheckbox checkBox = (GwtCheckbox) this.domContainer.getDocumentRoot().createElement("checkbox");
        checkBox.setDisabled(!column.isEditable());
        checkBox.layout();
        checkBox.setChecked(Boolean.parseBoolean(val));
        Binding bind = createBinding(cell, "value", checkBox, "checked");
        bind.setBindingType(Binding.Type.BI_DIRECTIONAL);
        domContainer.addBinding(bind);
        bind.fireSourceChanged();

        bind = createBinding(cell, "disabled", checkBox, "disabled");
        bind.setBindingType(Binding.Type.BI_DIRECTIONAL);
        domContainer.addBinding(bind);
        bind.fireSourceChanged();

        return (Widget) checkBox.getManagedObject();
      } catch (Exception e) {
        final CheckBox cb = new CheckBox();
        return cb;
      }

    } else if (colType.equalsIgnoreCase("combobox") || colType.equalsIgnoreCase("editablecombobox")) {
      try {
        final GwtMenuList glb = (GwtMenuList) this.domContainer.getDocumentRoot().createElement("menulist");

        final CustomListBox lb = glb.getNativeListBox();

        lb.setWidth("100%");
        final String fColType = colType;

        // Binding to elements of listbox not giving us the behavior we want. Menulists select the first available
        // item by default. We don't want anything selected by default in these cell editors
        PropertyChangeListener listener = new PropertyChangeListener() {
          public void propertyChange(PropertyChangeEvent evt) {

            Vector vals = (Vector) cell.getValue();
            lb.clear();
            lb.setSuppressLayout(true);
            for (Object label : vals) {
              lb.addItem(label.toString());
            }
            lb.setSuppressLayout(false);

            int idx = cell.getSelectedIndex();
            if (idx < 0) {
              idx = 0;
            }
            if (idx < vals.size()) {
              lb.setSelectedIndex(idx);
            }

            if (fColType.equalsIgnoreCase("editablecombobox")) {
              lb.setValue("");
            }
          }
        };
        listener.propertyChange(null);
        cell.addPropertyChangeListener("value", listener);

        lb.addChangeListener(new ChangeListener() {

          public void onChange(Widget arg0) {
            if (fColType.equalsIgnoreCase("editablecombobox")) {
              cell.setLabel(lb.getValue());
            } else {
              cell.setSelectedIndex(lb.getSelectedIndex());
            }
          }

        });

        if (colType.equalsIgnoreCase("editablecombobox")) {
          lb.setEditable(true);

          Binding bind = createBinding(cell, "selectedIndex", glb, "selectedIndex");
          bind.setBindingType(Binding.Type.BI_DIRECTIONAL);
          domContainer.addBinding(bind);
          if (cell.getLabel() == null) {
            bind.fireSourceChanged();
          }

          bind = createBinding(cell, "label", glb, "value");
          bind.setBindingType(Binding.Type.BI_DIRECTIONAL);
          domContainer.addBinding(bind);
          bind.fireSourceChanged();

        } else {

          Binding bind = createBinding(cell, "selectedIndex", glb, "selectedIndex");
          bind.setBindingType(Binding.Type.BI_DIRECTIONAL);
          domContainer.addBinding(bind);
          bind.fireSourceChanged();

        }

        Binding bind = createBinding(cell, "disabled", glb, "disabled");
        bind.setBindingType(Binding.Type.BI_DIRECTIONAL);
        domContainer.addBinding(bind);
        bind.fireSourceChanged();

        return lb;
      } catch (Exception e) {
        System.out.println("error creating menulist, fallback");

        final String fColType = colType;
        e.printStackTrace();
        final CustomListBox lb = new CustomListBox();

        lb.setWidth("100%");

        Vector vals = (Vector) cell.getValue();
        lb.setSuppressLayout(true);
        for (Object label : vals) {
          lb.addItem(label.toString());
        }
        lb.setSuppressLayout(false);
        lb.addChangeListener(new ChangeListener() {

          public void onChange(Widget arg0) {
            if (fColType.equalsIgnoreCase("editablecombobox")) {
              cell.setLabel(lb.getValue());
            } else {
              cell.setSelectedIndex(lb.getSelectedIndex());
            }
          }

        });

        int idx = cell.getSelectedIndex();
        if (idx < 0) {
          idx = 0;
        }
        if (idx < vals.size()) {
          lb.setSelectedIndex(idx);
        }
        if (fColType.equalsIgnoreCase("editablecombobox")) {
          lb.setEditable(true);
        }
        lb.setValue(cell.getLabel());

        return lb;
      }
    } else if (colType != null && customEditors.containsKey(colType)) {
      if (this.customRenderers.containsKey(colType)) {
        return new CustomCellEditorWrapper(cell, customEditors.get(colType), customRenderers.get(colType));
      } else {
        return new CustomCellEditorWrapper(cell, customEditors.get(colType));
      }

    } else {
      if (val == null || val.equals("")) {
        return new HTML("&nbsp;");
      }
      return new HTML(val);
    }

  }

  private int curSelectedRow = -1;

  private void setupTable() {
    List<XulComponent> colCollection = getColumns().getChildNodes();
    String cols[] = new String[colCollection.size()];

    SelectionGrid.SelectionPolicy selectionPolicy = null;
    if ("single".equals(getSeltype())) {
      selectionPolicy = SelectionGrid.SelectionPolicy.ONE_ROW;
    } else if ("multiple".equals(getSeltype())) {
      selectionPolicy = SelectionGrid.SelectionPolicy.MULTI_ROW;
    }

    int[] widths = new int[cols.length];
    int totalFlex = 0;

    for (int i = 0; i < cols.length; i++) {
      totalFlex += colCollection.get(i).getFlex();
    }

    boolean allFlexing = true;
    int totalWidth = 0;
    for (int i = 0; i < cols.length; i++) {
      cols[i] = ((XulTreeCol) colCollection.get(i)).getLabel();
      if (totalFlex > 0 && getWidth() > 0) {
        widths[i] = (int) (getWidth() * ((double) colCollection.get(i).getFlex() / totalFlex));
        totalWidth += widths[i];
      } else if (getColumns().getColumn(i).getWidth() > 0) {
        allFlexing = false;
        widths[i] = getColumns().getColumn(i).getWidth();
        totalWidth += widths[i];
      }
    }

    table = new BaseTable(cols, widths, new BaseColumnComparator[cols.length], selectionPolicy, this);

    if (getHeight() != 0) {
      table.setHeight(getHeight() + "px");
    } else {
      table.setHeight("100%");
    }
    if (getWidth() != 0) {
      table.setWidth(getWidth() + "px");
    } else {
      table.setWidth("100%");
    }
    if (allFlexing) {
      table.fillWidth();
    }


    RowSelectionHandler handler = new RowSelectionHandler() {
      @Override
      public void onRowSelection(RowSelectionEvent event) {
        //To change body of implemented methods use File | Settings | File Templates.
        try {

            if (getOnselect() != null && getOnselect().trim().length() > 0) {
              getXulDomContainer().invoke(getOnselect(), new Object[] {});
            }
            Integer[] selectedRows = table.getSelectedRows().toArray(new Integer[table.getSelectedRows().size()]);
            // set.toArray(new Integer[]) doesn't unwrap ><
            int[] rows = new int[selectedRows.length];
            for (int i = 0; i < selectedRows.length; i++) {
              rows[i] = selectedRows[i];
            }
            GwtTree.this.setSelectedRows(rows);<commit_msg>BISERVER-10181: Fixed problem with onSelect code for tree items.<commit_after>package org.pentaho.ui.xul.gwt.tags;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Vector;

import org.pentaho.gwt.widgets.client.buttons.ImageButton;
import org.pentaho.gwt.widgets.client.listbox.CustomListBox;
import org.pentaho.gwt.widgets.client.table.BaseTable;
import org.pentaho.gwt.widgets.client.table.ColumnComparators.BaseColumnComparator;
import org.pentaho.gwt.widgets.client.table.TableColumnSortListener;
import org.pentaho.gwt.widgets.client.ui.Draggable;
import org.pentaho.gwt.widgets.client.utils.string.StringUtils;
import org.pentaho.ui.xul.XulComponent;
import org.pentaho.ui.xul.XulDomContainer;
import org.pentaho.ui.xul.XulEventSource;
import org.pentaho.ui.xul.XulException;
import org.pentaho.ui.xul.binding.Binding;
import org.pentaho.ui.xul.binding.BindingConvertor;
import org.pentaho.ui.xul.binding.InlineBindingExpression;
import org.pentaho.ui.xul.components.XulTreeCell;
import org.pentaho.ui.xul.components.XulTreeCol;
import org.pentaho.ui.xul.containers.XulRoot;
import org.pentaho.ui.xul.containers.XulTree;
import org.pentaho.ui.xul.containers.XulTreeChildren;
import org.pentaho.ui.xul.containers.XulTreeCols;
import org.pentaho.ui.xul.containers.XulTreeItem;
import org.pentaho.ui.xul.containers.XulTreeRow;
import org.pentaho.ui.xul.dnd.DataTransfer;
import org.pentaho.ui.xul.dnd.DropEvent;
import org.pentaho.ui.xul.dnd.DropPosition;
import org.pentaho.ui.xul.dom.Document;
import org.pentaho.ui.xul.dom.Element;
import org.pentaho.ui.xul.gwt.AbstractGwtXulContainer;
import org.pentaho.ui.xul.gwt.GwtXulHandler;
import org.pentaho.ui.xul.gwt.GwtXulParser;
import org.pentaho.ui.xul.gwt.binding.GwtBinding;
import org.pentaho.ui.xul.gwt.binding.GwtBindingContext;
import org.pentaho.ui.xul.gwt.binding.GwtBindingMethod;
import org.pentaho.ui.xul.gwt.tags.util.TreeItemWidget;
import org.pentaho.ui.xul.gwt.util.Resizable;
import org.pentaho.ui.xul.gwt.util.XulDragController;
import org.pentaho.ui.xul.impl.XulEventHandler;
import org.pentaho.ui.xul.stereotype.Bindable;
import org.pentaho.ui.xul.util.TreeCellEditor;
import org.pentaho.ui.xul.util.TreeCellEditorCallback;
import org.pentaho.ui.xul.util.TreeCellRenderer;

import com.allen_sauer.gwt.dnd.client.DragContext;
import com.allen_sauer.gwt.dnd.client.VetoDragException;
import com.allen_sauer.gwt.dnd.client.drop.AbstractPositioningDropController;
import com.google.gwt.core.client.GWT;
import com.google.gwt.gen2.table.client.SelectionGrid;
import com.google.gwt.gen2.table.event.client.RowSelectionEvent;
import com.google.gwt.gen2.table.event.client.RowSelectionHandler;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.ChangeListener;
import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.KeyboardListener;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.Tree;
import com.google.gwt.user.client.ui.TreeItem;
import com.google.gwt.user.client.ui.TreeListener;
import com.google.gwt.user.client.ui.Widget;

public class GwtTree extends AbstractGwtXulContainer implements XulTree, Resizable, TableColumnSortListener {

  /**
   * Cached elements.
   */
  private Collection elements;
  private GwtBindingMethod dropVetoerMethod;
  private XulEventHandler dropVetoerController;

  public static void register() {
    GwtXulParser.registerHandler("tree", new GwtXulHandler() {
      public Element newInstance() {
        return new GwtTree();
      }
    });
  }

  XulTreeCols columns = null;
  XulTreeChildren rootChildren = null;
  private XulDomContainer domContainer;
  private Tree tree;

  private boolean suppressEvents = false;
  private boolean editable = false;
  private boolean visible = true;
  private boolean isSortable = false;
  private String command;

  private Map<String, TreeCellEditor> customEditors = new HashMap<String, TreeCellEditor>();
  private Map<String, TreeCellRenderer> customRenderers = new HashMap<String, TreeCellRenderer>();

  private List<Binding> elementBindings = new ArrayList<Binding>();
  private List<Binding> expandBindings = new ArrayList<Binding>();
  private boolean suppressExpansionScrollEvents;
  private DropPositionIndicator dropPosIndicator = new DropPositionIndicator();

  private boolean showAllEditControls = true;

  private boolean dropIconsVisible = true;
  private String onedit, onselect, seltype;

  @Bindable
  public boolean isVisible() {
    return visible;
  }

  @Bindable
  public void setVisible(boolean visible) {
    this.visible = visible;
    if (simplePanel != null) {
      simplePanel.setVisible(visible);
    }
  }

  /**
   * @return the tree managed by this GwtTree
   */
  public Tree getTree() {
    return tree;
  }

  /**
   * Used when this widget is a tree. Not used when this widget is a table.
   */
  private FlowPanel scrollPanel = new FlowPanel();
  {
    scrollPanel.setStylePrimaryName("tree-scroll-panel");
  }

  /**
   * The managed object. If this widget is a tree, then the tree is added to the scrollPanel, which is added to this simplePanel. If this widget is a table,
   * then the table is added directly to this simplePanel.
   */
  private SimplePanel simplePanel = new SimplePanel();

  /**
   * Clears the parent panel and adds the given widget.
   * 
   * @param widget
   *          tree or table to set in parent panel
   */
  protected void setWidgetInPanel(final Widget widget) {
    if (isHierarchical()) {
      scrollPanel.clear();
      simplePanel.add(scrollPanel);
      scrollPanel.add(widget);
      scrollPanel.add(dropPosIndicator);
    } else {
      simplePanel.clear();
      simplePanel.add(widget);
    }
  }

  public GwtTree() {
    super("tree");

    // managedObject is neither a native GWT tree nor a table since the entire native GWT object is thrown away each
    // time we call setup{Tree|Table}; because the widget is thrown away, we need to reconnect the new widget to the
    // simplePanel, which is the managedObject
    setManagedObject(simplePanel);

  }

  public void init(com.google.gwt.xml.client.Element srcEle, XulDomContainer container) {
    super.init(srcEle, container);
    setOnselect(srcEle.getAttribute("onselect"));
    setOnedit(srcEle.getAttribute("onedit"));
    setSeltype(srcEle.getAttribute("seltype"));

    if (StringUtils.isEmpty(srcEle.getAttribute("pen:ondrop")) == false) {
      setOndrop(srcEle.getAttribute("pen:ondrop"));
    }
    if (StringUtils.isEmpty(srcEle.getAttribute("pen:ondrag")) == false) {
      setOndrag(srcEle.getAttribute("pen:ondrag"));
    }
    if (StringUtils.isEmpty(srcEle.getAttribute("pen:drageffect")) == false) {
      setDrageffect(srcEle.getAttribute("pen:drageffect"));
    }

    setDropvetoer(srcEle.getAttribute("pen:dropvetoer"));
    if (StringUtils.isEmpty(srcEle.getAttribute("pen:showalleditcontrols")) == false) {
      this.setShowalleditcontrols(srcEle.getAttribute("pen:showalleditcontrols").equals("true"));
    }
    this.setEditable("true".equals(srcEle.getAttribute("editable")));

    if (StringUtils.isEmpty(srcEle.getAttribute("pen:dropiconsvisible")) == false) {
      this.setDropIconsVisible(srcEle.getAttribute("pen:dropiconsvisible").equals("true"));
    }

    this.domContainer = container;
  }

  private List<TreeItemDropController> dropHandlers = new ArrayList<TreeItemDropController>();

  public void addChild(Element element) {
    super.addChild(element);
    if (element.getName().equals("treecols")) {
      columns = (XulTreeCols) element;
    } else if (element.getName().equals("treechildren")) {
      rootChildren = (XulTreeChildren) element;
    }
  }

  public void addTreeRow(XulTreeRow row) {
    GwtTreeItem item = new GwtTreeItem();
    item.setRow(row);
    this.rootChildren.addItem(item);

    // update UI
    updateUI();
  }

  private BaseTable table;
  private boolean isHierarchical = false;
  private boolean firstLayout = true;
  private Object[][] currentData;

  // need to handle layouting
  public void layout() {
    if (this.getRootChildren() == null) {
      // most likely an overlay
      return;
    }
    if (firstLayout) {

      XulTreeItem item = (XulTreeItem) this.getRootChildren().getFirstChild();

      if (item != null && item.getAttributeValue("container") != null && item.getAttributeValue("container").equals("true")) {
        isHierarchical = true;
      }
      firstLayout = false;
    }

    if (isHierarchical()) {
      setupTree();
    } else {
      setupTable();
    }
    setVisible(visible);
  }

  private int prevSelectionPos = -1;

  private void setupTree() {
    if (tree == null) {
      tree = new Tree();
      setWidgetInPanel(tree);
    }
    if (getWidth() > 0) {
      scrollPanel.setWidth(getWidth() + "px"); //$NON-NLS-1$
    }
    if (getHeight() > 0) {
      scrollPanel.setHeight(getHeight() + "px"); //$NON-NLS-1$
    }

    tree.addTreeListener(new TreeListener() {

      public void onTreeItemSelected(TreeItem arg0) {

        String jscmd = ((GwtTreeItem) ((TreeItemWidget) arg0.getWidget()).getTreeItem()).getJsCommand();
        if (!StringUtils.isEmpty(jscmd)) {
          executeJS(jscmd);
        } else {
          String cmd = ((GwtTreeItem) ((TreeItemWidget) arg0.getWidget()).getTreeItem()).getCommand();
          if (!StringUtils.isEmpty(cmd)) {
            invoke(cmd);
          }
        }

        int pos = -1;
        int curPos = 0;
        for (int i = 0; i < tree.getItemCount(); i++) {
          TreeItem tItem = tree.getItem(i);
          TreeCursor cursor = GwtTree.this.findPosition(tItem, arg0, curPos);
          pos = cursor.foundPosition;
          curPos = cursor.curPos + 1;
          if (pos > -1) {
            break;
          }
        }

        if (pos > -1 && GwtTree.this.suppressEvents == false && prevSelectionPos != pos) {
          GwtTree.this.changeSupport.firePropertyChange("selectedRows", null, new int[] { pos });
          GwtTree.this.changeSupport.firePropertyChange("absoluteSelectedRows", null, new int[] { pos });
          GwtTree.this.fireSelectedItems();
        }
        prevSelectionPos = pos;

      }

      public void onTreeItemStateChanged(TreeItem item) {
        ((TreeItemWidget) item.getWidget()).getTreeItem().setExpanded(item.getState());
      }

    });
    updateUI();
  }

  private class TreeCursor {
    int foundPosition = -1;
    int curPos;
    TreeItem itemToFind;

    public TreeCursor(int curPos, TreeItem itemToFind, int foundPosition) {
      this.foundPosition = foundPosition;
      this.curPos = curPos;
      this.itemToFind = itemToFind;
    }
  }

  private TreeCursor findPosition(TreeItem curItem, TreeItem itemToFind, int curPos) {
    if (curItem == itemToFind) {
      TreeCursor p = new TreeCursor(curPos, itemToFind, curPos);
      return p;
      // } else if(curItem.getChildIndex(itemToFind) > -1) {
      // curPos = curPos+1;
      // return new TreeCursor(curPos+curItem.getChildIndex(itemToFind) , itemToFind, curPos+curItem.getChildIndex(itemToFind));
    } else {
      for (int i = 1; i - 1 < curItem.getChildCount(); i++) {
        TreeCursor p = findPosition(curItem.getChild(i - 1), itemToFind, curPos + 1);
        curPos = p.curPos;
        if (p.foundPosition > -1) {
          return p;
        }
      }
      // curPos += curItem.getChildCount() ;
      return new TreeCursor(curPos, itemToFind, -1);
    }

  }

  private void populateTree() {
    tree.removeItems();
    TreeItem topNode = new TreeItem("placeholder");
    if (this.rootChildren == null) {
      this.rootChildren = (XulTreeChildren) this.children.get(1);
    }
    for (XulComponent c : this.rootChildren.getChildNodes()) {
      XulTreeItem item = (XulTreeItem) c;
      TreeItem node = createNode(item);
      tree.addItem(node);
    }

    if (StringUtils.isEmpty(this.ondrag) == false) {
      for (int i = 0; i < tree.getItemCount(); i++) {
        madeDraggable(tree.getItem(i));
      }
    }

  }

  private void madeDraggable(TreeItem item) {

    XulDragController.getInstance().makeDraggable(item.getWidget());

    for (int i = 0; i < item.getChildCount(); i++) {
      madeDraggable(item.getChild(i));
    }
  }

  private List<XulComponent> colCollection = new ArrayList<XulComponent>();

  private void populateTable() {

    int rowCount = getRootChildren().getItemCount();
    // Getting column editors ends up calling getChildNodes several times. we're going to cache the column collections
    // for the duration of the operation then clear it out.
    colCollection = getColumns().getChildNodes();
    int colCount = colCollection.size();

    currentData = new Object[rowCount][colCount];

    for (int i = 0; i < rowCount; i++) {
      for (int j = 0; j < colCount; j++) {
        currentData[i][j] = getColumnEditor(j, i);
      }
    }

    table.populateTable(currentData, elements);
    int totalFlex = 0;
    boolean allFlexing = true;
    for (int i = 0; i < colCount; i++) {
      XulTreeCol col = (XulTreeCol) colCollection.get(i);
      // Get the direction of the sort
      String sortDirection = col.getSortDirection();
      // if any column has sortActive flag on, then we will enable the sorting on a table
      // Also we will sort that column by default with a provided direction.
      if (col.isSortActive()) {
        isSortable = true;
        table.setSortingEnabled(true);
        table.sortColumn(i, sortDirection != null && sortDirection.equals("ASCENDING") ? true : false); //$NON-NLS-1$
      }
      int fx = colCollection.get(i).getFlex();
      totalFlex += fx;
      if (fx == 0) {
        allFlexing = false;
      }
    }
    // If the table is sortable then set all columns to be sortable. This enables the sorting by clicking the column header
    if (isSortable) {
      for (int i = 0; i < colCount; i++) {
        table.setColumnSortable(i, true);
      }
    }
    if (totalFlex > 0) {
      table.fillWidth();
    } else {
      table.noFill();
    }
    // If all flexing need to hide horizontal scrolling to fix an IE7 issue
    if (allFlexing) {
      table.suppressHorizontalScrolling();
    }
    colCollection = new ArrayList<XulComponent>();

    if (this.selectedRows != null && this.selectedRows.length > 0) {
      for (int i = 0; i < this.selectedRows.length; i++) {
        int idx = this.selectedRows[i];
        if (idx > -1 && idx < currentData.length) {
          table.selectRow(idx);
        }
      }
    }
  }

  private TreeItem createNode(final XulTreeItem item) {
    TreeItem node = new TreeItem("empty") {
      @Override
      public void setSelected(boolean selected) {
        super.setSelected(selected);
        if (selected) {
          this.getWidget().addStyleDependentName("selected");
        } else {
          this.getWidget().removeStyleDependentName("selected");
        }
      }
    };
    item.setManagedObject(node);
    if (item == null || item.getRow() == null || item.getRow().getChildNodes().size() == 0) {
      return node;
    }
    TreeItemWidget tempWidget = new TreeItemWidget(item);
    tempWidget.setDropIconsVisible(isDropIconsVisible());
    final TreeItemWidget tWidget = tempWidget;

    final String spacerPath = "images/spacer.gif";

    PropertyChangeListener listener = new PropertyChangeListener() {
      public void propertyChange(PropertyChangeEvent evt) {
        if (evt.getPropertyName().equals("image") && item.getImage() != null) {
          Image img = new Image(GWT.getModuleBaseURL() + item.getImage());
          if (item.getClassname() != null) {
            img.setUrl(GWT.getModuleBaseURL() + spacerPath);
          }
          tWidget.setImage(img);
        } else if (evt.getPropertyName().equals("label")) {
          tWidget.setLabel(item.getRow().getCell(0).getLabel());
        } else if (evt.getPropertyName().equals("classname") && item.getImage() != null) {
          tWidget.getImage().removeStyleName((String) evt.getOldValue());
          tWidget.getImage().addStyleName(item.getClassname());
        }
      }
    };

    ((GwtTreeItem) item).addPropertyChangeListener("image", listener);
    ((GwtTreeItem) item).addPropertyChangeListener("classname", listener);
    ((GwtTreeCell) item.getRow().getCell(0)).addPropertyChangeListener("label", listener);

    if (item.getImage() != null) {
      Image img = new Image(GWT.getModuleBaseURL() + item.getImage());

      if (item.getClassname() != null) {
        img.addStyleName(item.getClassname());
        img.setUrl(GWT.getModuleBaseURL() + spacerPath);
      }

      tWidget.setImage(img);
    }



    tWidget.setLabel(item.getRow().getCell(0).getLabel());
    if (this.ondrop != null) {
      TreeItemDropController controller = new TreeItemDropController(item, tWidget, this);
      XulDragController.getInstance().registerDropController(controller);
      this.dropHandlers.add(controller);
    }

    node.setWidget(tWidget);

    if (item.getChildNodes().size() > 1) {
      // has children
      // TODO: make this more defensive
      XulTreeChildren children = (XulTreeChildren) item.getChildNodes().get(1);
      for (XulComponent c : children.getChildNodes()) {

        TreeItem childNode = createNode((XulTreeItem) c);
        node.addItem(childNode);
      }
    }
    return node;
  }

  private String extractDynamicColType(Object row, int columnPos) {
    GwtBindingMethod method = GwtBindingContext.typeController.findGetMethod(row, this.columns.getColumn(columnPos).getColumntypebinding());
    try {
      return (String) method.invoke(row, new Object[] {});
    } catch (Exception e) {
      System.out.println("Could not extract column type from binding");
    }
    return "text"; // default //$NON-NLS-1$
  }

  private Binding createBinding(XulEventSource source, String prop1, XulEventSource target, String prop2) {
    if (bindingProvider != null) {
      return bindingProvider.getBinding(source, prop1, target, prop2);
    }
    return new GwtBinding(source, prop1, target, prop2);
  }

  private Widget getColumnEditor(final int x, final int y) {

    final XulTreeCol column = (XulTreeCol) colCollection.get(x);
    String colType = ((XulTreeCol) colCollection.get(x)).getType();
    XulTreeItem item = getRootChildren().getItem(y);
    if(item == null){
      return new HTML("");
    }
    XulTreeRow row1 = item.getRow();
    if(row1 == null){
      return new HTML("");
    }
    final GwtTreeCell cell = (GwtTreeCell) (row1.getChildNodes().size() > x ? row1.getCell(x) : null);
    if(cell == null){
      return new HTML("");
    }
    String val = cell.getLabel();

    // If collection bound, bindings may need to be updated for runtime changes in column types.
    if (this.elements != null) {
      try {
        this.addBindings(column, cell, this.elements.toArray()[y]);
      } catch (InvocationTargetException e) {
        e.printStackTrace();
      } catch (XulException e) {
        e.printStackTrace();
      }
    }

    if (StringUtils.isEmpty(colType) == false && colType.equalsIgnoreCase("dynamic")) {
      Object row = elements.toArray()[y];
      colType = extractDynamicColType(row, x);
    }

    int[] selectedRows = getSelectedRows();

    if ((StringUtils.isEmpty(colType) || !column.isEditable())
        || ((showAllEditControls == false && (selectedRows.length != 1 || selectedRows[0] != y)) && !colType.equals("checkbox"))) {
      if (colType != null && (colType.equalsIgnoreCase("combobox") || colType.equalsIgnoreCase("editablecombobox"))) {
        Vector vals = (Vector) cell.getValue();
        int idx = cell.getSelectedIndex();
        if (colType.equalsIgnoreCase("editablecombobox")) {
          return new HTML(cell.getLabel());
        } else if (idx < vals.size()) {
          return new HTML("" + vals.get(idx));
        } else {
          return new HTML("");
        }
      } else {
        return new HTML(val);
      }
    } else if (colType.equalsIgnoreCase("text")) {

      try {

        GwtTextbox b = (GwtTextbox) this.domContainer.getDocumentRoot().createElement("textbox");
        b.setDisabled(!column.isEditable());
        b.layout();
        b.setValue(val);
        Binding bind = createBinding(cell, "label", b, "value");
        bind.setBindingType(Binding.Type.BI_DIRECTIONAL);
        domContainer.addBinding(bind);
        bind.fireSourceChanged();

        bind = createBinding(cell, "disabled", b, "disabled");
        bind.setBindingType(Binding.Type.BI_DIRECTIONAL);
        domContainer.addBinding(bind);
        bind.fireSourceChanged();

        return (Widget) b.getManagedObject();
      } catch (Exception e) {
        System.out.println("error creating textbox, fallback");
        e.printStackTrace();
        final TextBox b = new TextBox();
        b.addKeyboardListener(new KeyboardListener() {

          public void onKeyDown(Widget arg0, char arg1, int arg2) {
          }

          public void onKeyPress(Widget arg0, char arg1, int arg2) {
          }

          public void onKeyUp(Widget arg0, char arg1, int arg2) {
            getRootChildren().getItem(y).getRow().getCell(x).setLabel(b.getText());
          }

        });
        b.setText(val);
        return b;
      }
    } else if (colType.equalsIgnoreCase("checkbox")) {
      try {
        GwtCheckbox checkBox = (GwtCheckbox) this.domContainer.getDocumentRoot().createElement("checkbox");
        checkBox.setDisabled(!column.isEditable());
        checkBox.layout();
        checkBox.setChecked(Boolean.parseBoolean(val));
        Binding bind = createBinding(cell, "value", checkBox, "checked");
        bind.setBindingType(Binding.Type.BI_DIRECTIONAL);
        domContainer.addBinding(bind);
        bind.fireSourceChanged();

        bind = createBinding(cell, "disabled", checkBox, "disabled");
        bind.setBindingType(Binding.Type.BI_DIRECTIONAL);
        domContainer.addBinding(bind);
        bind.fireSourceChanged();

        return (Widget) checkBox.getManagedObject();
      } catch (Exception e) {
        final CheckBox cb = new CheckBox();
        return cb;
      }

    } else if (colType.equalsIgnoreCase("combobox") || colType.equalsIgnoreCase("editablecombobox")) {
      try {
        final GwtMenuList glb = (GwtMenuList) this.domContainer.getDocumentRoot().createElement("menulist");

        final CustomListBox lb = glb.getNativeListBox();

        lb.setWidth("100%");
        final String fColType = colType;

        // Binding to elements of listbox not giving us the behavior we want. Menulists select the first available
        // item by default. We don't want anything selected by default in these cell editors
        PropertyChangeListener listener = new PropertyChangeListener() {
          public void propertyChange(PropertyChangeEvent evt) {

            Vector vals = (Vector) cell.getValue();
            lb.clear();
            lb.setSuppressLayout(true);
            for (Object label : vals) {
              lb.addItem(label.toString());
            }
            lb.setSuppressLayout(false);

            int idx = cell.getSelectedIndex();
            if (idx < 0) {
              idx = 0;
            }
            if (idx < vals.size()) {
              lb.setSelectedIndex(idx);
            }

            if (fColType.equalsIgnoreCase("editablecombobox")) {
              lb.setValue("");
            }
          }
        };
        listener.propertyChange(null);
        cell.addPropertyChangeListener("value", listener);

        lb.addChangeListener(new ChangeListener() {

          public void onChange(Widget arg0) {
            if (fColType.equalsIgnoreCase("editablecombobox")) {
              cell.setLabel(lb.getValue());
            } else {
              cell.setSelectedIndex(lb.getSelectedIndex());
            }
          }

        });

        if (colType.equalsIgnoreCase("editablecombobox")) {
          lb.setEditable(true);

          Binding bind = createBinding(cell, "selectedIndex", glb, "selectedIndex");
          bind.setBindingType(Binding.Type.BI_DIRECTIONAL);
          domContainer.addBinding(bind);
          if (cell.getLabel() == null) {
            bind.fireSourceChanged();
          }

          bind = createBinding(cell, "label", glb, "value");
          bind.setBindingType(Binding.Type.BI_DIRECTIONAL);
          domContainer.addBinding(bind);
          bind.fireSourceChanged();

        } else {

          Binding bind = createBinding(cell, "selectedIndex", glb, "selectedIndex");
          bind.setBindingType(Binding.Type.BI_DIRECTIONAL);
          domContainer.addBinding(bind);
          bind.fireSourceChanged();

        }

        Binding bind = createBinding(cell, "disabled", glb, "disabled");
        bind.setBindingType(Binding.Type.BI_DIRECTIONAL);
        domContainer.addBinding(bind);
        bind.fireSourceChanged();

        return lb;
      } catch (Exception e) {
        System.out.println("error creating menulist, fallback");

        final String fColType = colType;
        e.printStackTrace();
        final CustomListBox lb = new CustomListBox();

        lb.setWidth("100%");

        Vector vals = (Vector) cell.getValue();
        lb.setSuppressLayout(true);
        for (Object label : vals) {
          lb.addItem(label.toString());
        }
        lb.setSuppressLayout(false);
        lb.addChangeListener(new ChangeListener() {

          public void onChange(Widget arg0) {
            if (fColType.equalsIgnoreCase("editablecombobox")) {
              cell.setLabel(lb.getValue());
            } else {
              cell.setSelectedIndex(lb.getSelectedIndex());
            }
          }

        });

        int idx = cell.getSelectedIndex();
        if (idx < 0) {
          idx = 0;
        }
        if (idx < vals.size()) {
          lb.setSelectedIndex(idx);
        }
        if (fColType.equalsIgnoreCase("editablecombobox")) {
          lb.setEditable(true);
        }
        lb.setValue(cell.getLabel());

        return lb;
      }
    } else if (colType != null && customEditors.containsKey(colType)) {
      if (this.customRenderers.containsKey(colType)) {
        return new CustomCellEditorWrapper(cell, customEditors.get(colType), customRenderers.get(colType));
      } else {
        return new CustomCellEditorWrapper(cell, customEditors.get(colType));
      }

    } else {
      if (val == null || val.equals("")) {
        return new HTML("&nbsp;");
      }
      return new HTML(val);
    }

  }

  private int curSelectedRow = -1;

  private void setupTable() {
    List<XulComponent> colCollection = getColumns().getChildNodes();
    String cols[] = new String[colCollection.size()];

    SelectionGrid.SelectionPolicy selectionPolicy = null;
    if ("single".equals(getSeltype())) {
      selectionPolicy = SelectionGrid.SelectionPolicy.ONE_ROW;
    } else if ("multiple".equals(getSeltype())) {
      selectionPolicy = SelectionGrid.SelectionPolicy.MULTI_ROW;
    }

    int[] widths = new int[cols.length];
    int totalFlex = 0;

    for (int i = 0; i < cols.length; i++) {
      totalFlex += colCollection.get(i).getFlex();
    }

    boolean allFlexing = true;
    int totalWidth = 0;
    for (int i = 0; i < cols.length; i++) {
      cols[i] = ((XulTreeCol) colCollection.get(i)).getLabel();
      if (totalFlex > 0 && getWidth() > 0) {
        widths[i] = (int) (getWidth() * ((double) colCollection.get(i).getFlex() / totalFlex));
        totalWidth += widths[i];
      } else if (getColumns().getColumn(i).getWidth() > 0) {
        allFlexing = false;
        widths[i] = getColumns().getColumn(i).getWidth();
        totalWidth += widths[i];
      }
    }

    table = new BaseTable(cols, widths, new BaseColumnComparator[cols.length], selectionPolicy, this);

    if (getHeight() != 0) {
      table.setHeight(getHeight() + "px");
    } else {
      table.setHeight("100%");
    }
    if (getWidth() != 0) {
      table.setWidth(getWidth() + "px");
    } else {
      table.setWidth("100%");
    }
    if (allFlexing) {
      table.fillWidth();
    }


    RowSelectionHandler handler = new RowSelectionHandler() {
      @Override
      public void onRowSelection(RowSelectionEvent event) {
        //To change body of implemented methods use File | Settings | File Templates.
        try {	
            Integer[] selectedRows = table.getSelectedRows().toArray(new Integer[table.getSelectedRows().size()]);
            
            if (getOnselect() != null && getOnselect().trim().length() > 0) {
              getXulDomContainer().invoke(getOnselect(), new Object[] {selectedRows.length > 0 ? selectedRows[0] : null});
            }
            
            // set.toArray(new Integer[]) doesn't unwrap ><
            int[] rows = new int[selectedRows.length];
            for (int i = 0; i < selectedRows.length; i++) {
              rows[i] = selectedRows[i];
            }
            GwtTree.this.setSelectedRows(rows);
{'fraction_non_alphanumeric': 0.12649999151348507, 'fraction_numerical': 0.0035134171801008197, 'mean_word_length': 2.9410033444816053, 'pattern_counts': {'":': 0, '<': 69, '<?xml version=': 0, '>': 64, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 2}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '255396', 'n_tokens_mistral': 18006, 'n_tokens_neox': 17639, 'n_words': 3697}