Java tutorial
/*! * This program is free software; you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software * Foundation. * * You should have received a copy of the GNU Lesser General Public License along with this * program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html * or from the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU Lesser General Public License for more details. * * Copyright (c) 2002-2013 Pentaho Corporation.. All rights reserved. */ package org.pentaho.ui.xul.swing.tags; import java.awt.Color; import java.awt.Component; import java.awt.Dimension; import java.awt.Rectangle; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ComponentEvent; import java.awt.event.ComponentListener; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Vector; import javax.swing.CellEditor; import javax.swing.DefaultCellEditor; import javax.swing.DefaultComboBoxModel; import javax.swing.JCheckBox; import javax.swing.JComboBox; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JPopupMenu; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.JTextField; import javax.swing.JTree; import javax.swing.ListSelectionModel; import javax.swing.SwingUtilities; import javax.swing.ToolTipManager; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import javax.swing.event.TreeSelectionEvent; import javax.swing.event.TreeSelectionListener; import javax.swing.table.AbstractTableModel; import javax.swing.table.DefaultTableCellRenderer; import javax.swing.table.TableCellEditor; import javax.swing.table.TableCellRenderer; import javax.swing.table.TableColumn; import javax.swing.table.TableColumnModel; import javax.swing.table.TableModel; import javax.swing.text.JTextComponent; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.DefaultTreeCellRenderer; import javax.swing.tree.DefaultTreeModel; import javax.swing.tree.TreeModel; import javax.swing.tree.TreeNode; import javax.swing.tree.TreePath; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.pentaho.ui.xul.XulComponent; import org.pentaho.ui.xul.XulDomContainer; import org.pentaho.ui.xul.XulDomException; 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.XulManagedCollection; 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.dom.Element; import org.pentaho.ui.xul.swing.AbstractSwingContainer; import org.pentaho.ui.xul.swing.SwingBinding; import org.pentaho.ui.xul.swing.messages.Messages; import org.pentaho.ui.xul.util.ColumnType; import org.pentaho.ui.xul.util.TreeCellEditorCallback; import org.pentaho.ui.xul.util.TreeCellRenderer; public class SwingTree extends AbstractSwingContainer implements XulTree { private JTable table; private JTree tree; private ListSelectionListener selectionListener; private TreeSelectionListener treeSelectionListener; MouseListener popupListener; private JScrollPane scrollpane; private TableColumnModel columnModel; private TableModel tableModel; private XulTreeChildren rootChildren; private boolean columnDrag = true; private boolean disabled = false; private boolean editable = false; private String onselect; private String onedit = null; private static final Log logger = LogFactory.getLog(SwingTree.class); private Vector<String> columnNames = new Vector<String>(); private boolean isHierarchical = false; private Map<String, org.pentaho.ui.xul.util.TreeCellEditor> customEditors = new HashMap<String, org.pentaho.ui.xul.util.TreeCellEditor>(); // TODO: migrate to XulTree interface public enum SELECTION_MODE { SINGLE, CELL, MULTIPLE, NONE }; private SELECTION_MODE selType = SELECTION_MODE.SINGLE; private XulDomContainer domContainer; private String command; private String newItemBinding; private boolean autoCreateNewRows; JPopupMenu popupMenu = new JPopupMenu(); JMenuItem addRowMenu = new JMenuItem(Messages.getString("SwingTree.insert")); //$NON-NLS-1$ JMenuItem deleteRowMenu = new JMenuItem(Messages.getString("SwingTree.deleteRow")); //$NON-NLS-1$ JMenuItem deleteRowsMenu = new JMenuItem(Messages.getString("SwingTree.deleteRows")); //$NON-NLS-1$ JMenuItem keepOnlyRowsMenu = new JMenuItem(Messages.getString("SwingTree.keepOnlyRows")); //$NON-NLS-1$ public SwingTree(Element self, XulComponent parent, XulDomContainer domContainer, String tagName) { super("tree"); //$NON-NLS-1$ this.domContainer = domContainer; this.registerCellEditor("custom-editor", new CustomTreeCellEditor()); //$NON-NLS-1$ addRowMenu.addActionListener(new InsertRowsActionAdapter(this)); deleteRowMenu.addActionListener(new DeleteRowsActionAdapter(this)); deleteRowsMenu.addActionListener(new DeleteRowsActionAdapter(this)); keepOnlyRowsMenu.addActionListener(new KeepOnlyRowsActionAdapter(this)); popupMenu.add(addRowMenu); popupMenu.add(deleteRowMenu); popupMenu.add(deleteRowsMenu); popupMenu.add(keepOnlyRowsMenu); } public JTable getTable() { return table; } public JTree getTree() { return tree; } public int[] getActiveCellCoordinates() { return new int[] { table.getSelectedRow(), table.getSelectedColumn() }; } public XulTreeCols getColumns() { return this.columns; } public String getOnselect() { return this.onselect; } public XulTreeChildren getRootChildren() { if (rootChildren == null) { rootChildren = (XulTreeChildren) this.getChildNodes().get(1); } return rootChildren; } public int getRows() { return getRootChildren().getItemCount(); } public String getSeltype() { return selType.toString(); } public Object[][] getValues() { TableModel model = table.getModel(); Object[][] data = new Object[getRootChildren().getChildNodes().size()][model.getColumnCount()]; int y = 0; for (XulComponent item : getRootChildren().getChildNodes()) { int x = 0; for (XulComponent tempCell : ((XulTreeItem) item).getRow().getChildNodes()) { SwingTreeCell cell = (SwingTreeCell) tempCell; switch (columns.getColumn(x).getColumnType()) { case CHECKBOX: Boolean flag = (Boolean) cell.getValue(); if (flag == null) { flag = Boolean.FALSE; } data[y][x] = flag; break; case COMBOBOX: Vector values = (Vector) cell.getValue(); int idx = cell.getSelectedIndex(); data[y][x] = values.get(idx); break; default: // label data[y][x] = cell.getLabel(); break; } x++; } y++; } // for(int row=0; row<this.rootChildren.getRowCount(); row++){ // for(int col=0; col<model.getColumnCount(); col++){ // data[row][col] = model.getValueAt(row,col); // } // } return data; } public int getWidth() { return scrollpane.getWidth(); } public void setWidth(int width) { if (tree == null || table == null) { return; } int height = (table != null) ? table.getHeight() : (tree != null) ? tree.getHeight() : scrollpane.getHeight(); Dimension dim = new Dimension(width, height); scrollpane.setPreferredSize(dim); scrollpane.setMaximumSize(dim); scrollpane.setSize(dim); } public boolean isDisabled() { return disabled; } public boolean isEditable() { return editable; } public boolean isEnableColumnDrag() { return this.columnDrag; } public boolean isHierarchical() { // TODO Auto-generated method stub return false; } public void setActiveCellCoordinates(int row, int column) { table.changeSelection(row, column, false, false); } public void setDisabled(boolean dis) { this.disabled = dis; if (table != null) { table.setEnabled(!this.disabled); } } public void setEditable(boolean edit) { this.editable = edit; } public void setEnableColumnDrag(boolean drag) { this.columnDrag = drag; if (table != null) { table.getTableHeader().setReorderingAllowed(drag); } } public void setOnselect(final String select) { selectionListener = new ListSelectionListener() { public void valueChanged(ListSelectionEvent event) { if (event.getValueIsAdjusting() == true) { return; } invoke(select, new Object[] { new Integer(table.getSelectedRow()) }); } }; if (table != null) { table.getSelectionModel().addListSelectionListener(selectionListener); } treeSelectionListener = new TreeSelectionListener() { public void valueChanged(TreeSelectionEvent e) { if (tree != null) { invoke(select, new Object[] { tree.getSelectionRows()[0] }); } } }; if (tree != null) { tree.getSelectionModel().addTreeSelectionListener(treeSelectionListener); } } private XulTreeCols columns; public void setColumns(XulTreeCols columns) { this.columns = columns; } public void setRootChildren(final XulTreeChildren rootChildren) { if (this.rootChildren == null) { this.rootChildren = rootChildren; } // for (XulComponent row : rootChildren.getChildNodes()) { // // XulTreeRow xrow = (XulTreeRow) row.getChildNodes().get(0); // addTreeRow(xrow); // // } } public void addTreeRow(XulTreeRow row) { getRootChildren().addItem(new SwingTreeItem(row)); table.updateUI(); } public void removeTreeRows(int[] rows) { // sort the rows high to low ArrayList<Integer> rowArray = new ArrayList<Integer>(); for (int i = 0; i < rows.length; i++) { rowArray.add(rows[i]); } Collections.sort(rowArray, Collections.reverseOrder()); // remove the items in that order for (int i = 0; i < rowArray.size(); i++) { int item = rowArray.get(i); if (item >= 0 && item < getRootChildren().getItemCount()) { getRootChildren().removeItem(item); } } table.updateUI(); // treat as selection change. changeSupport.firePropertyChange("selectedRows", null, getSelectedRows()); //$NON-NLS-1$ changeSupport.firePropertyChange("absoluteSelectedRows", null, getAbsoluteSelectedRows()); //$NON-NLS-1$ } private int rows = -1; public void setRows(int rows) { this.rows = rows; } public void setSeltype(String type) { this.selType = SELECTION_MODE.valueOf(type.toUpperCase()); if (table == null) { return; } switch (this.selType) { case CELL: table.setCellSelectionEnabled(true); break; case MULTIPLE: table.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); break; case SINGLE: table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); break; } } public void setTableModel(TableModel model) { this.tableModel = model; } private int totalFlex = 0; @Override public void layout() { XulComponent primaryColumn = this.getElementByXPath("//treecol[@primary='true']"); //$NON-NLS-1$ XulComponent isaContainer = this.getElementByXPath("treechildren/treeitem[@container='true']"); //$NON-NLS-1$ isHierarchical = (primaryColumn != null) || (isaContainer != null); if (isHierarchical) { tree = new JTree(); if (treeSelectionListener != null) { tree.getSelectionModel().addTreeSelectionListener(treeSelectionListener); } tree.getSelectionModel().addTreeSelectionListener(new TreeSelectionListener() { public void valueChanged(TreeSelectionEvent e) { SwingTree.this.changeSupport.firePropertyChange("selectedRows", null, //$NON-NLS-1$ SwingTree.this.getSelectedRows()); SwingTree.this.changeSupport.firePropertyChange("absoluteSelectedRows", null, //$NON-NLS-1$ SwingTree.this.getAbsoluteSelectedRows()); SwingTree.this.fireSelectedItem(); } }); } else { table = new ScrollableJTable(); if (selectionListener != null) { table.getSelectionModel().addListSelectionListener(selectionListener); } table.getSelectionModel().addListSelectionListener(new ListSelectionListener() { public void valueChanged(ListSelectionEvent event) { if (event.getValueIsAdjusting() == true) { return; } SwingTree.this.changeSupport.firePropertyChange("selectedRows", null, //$NON-NLS-1$ SwingTree.this.getSelectedRows()); SwingTree.this.fireSelectedItem(); } }); } JComponent comp = (table != null ? table : tree); ToolTipManager.sharedInstance().unregisterComponent(comp); if (table != null) { ToolTipManager.sharedInstance().unregisterComponent(table.getTableHeader()); table.setRowHeight(18); scrollpane = new JScrollPane(table); setupTable(); } else { setupTree(); scrollpane = new JScrollPane(tree); } setManagedObject(scrollpane.getViewport()); } int numOfListeners = 0; private void updateColumnModel() { TableColumnModel columnModel = table.getColumnModel(); totalFlex = 0; for (int i = 0; i < columns.getChildNodes().size(); i++) { if (i >= columnModel.getColumnCount()) { break; } SwingTreeCol child = (SwingTreeCol) columns.getChildNodes().get(i); TableColumn col = columnModel.getColumn(i); totalFlex += child.getFlex(); col.setHeaderValue(child.getLabel()); col.setCellEditor(getCellEditor(child)); col.setCellRenderer(getCellRenderer(child)); // List<XulComponent> cells = child.getChildNodes(); // for(int z=0; z<cells.size(); z++){ // XulComponent cell = cells.get(z); // } } } private void setupTable() { // generate table object based on TableModel and TableColumnModel // if(tableModel == null){ tableModel = new XulTableModel(this); // } table.setModel(this.tableModel); this.setSeltype(getSeltype()); updateColumnModel(); initialized = true; table.addComponentListener(new ComponentListener() { public void componentHidden(ComponentEvent arg0) { } public void componentMoved(ComponentEvent e) { } public void componentShown(ComponentEvent e) { } public void componentResized(ComponentEvent e) { calcColumnWidths(); } }); // Property change on selections table.getSelectionModel().addListSelectionListener(new ListSelectionListener() { public void valueChanged(ListSelectionEvent event) { if (event.getValueIsAdjusting() == true) { return; } SwingTree.this.changeSupport.firePropertyChange("selectedRows", null, //$NON-NLS-1$ SwingTree.this.getSelectedRows()); SwingTree.this.changeSupport.firePropertyChange("absoluteSelectedRows", null, //$NON-NLS-1$ SwingTree.this.getAbsoluteSelectedRows()); } }); table.getTableHeader().setReorderingAllowed(this.isEnableColumnDrag()); this.setDisabled(this.isDisabled()); } private void calcColumnWidths() { Rectangle size = table.getBounds(); int newWidth = size.width; if (SwingTree.this.rows > -1) { int minHeight = table.getRowHeight() * rows; scrollpane.getViewport().setMinimumSize(new Dimension(scrollpane.getWidth(), minHeight - 100)); } for (int i = 0; i < table.getColumnCount(); i++) { int newColWidth = 50; // reasonable default size if (totalFlex > 0) { int flex = SwingTree.this.columns.getColumn(table.getColumnModel().getColumn(i).getModelIndex()) .getFlex(); if (flex != 0) { newColWidth = (int) (newWidth * ((double) flex / totalFlex)); } } else { newColWidth = (int) (newWidth * ((double) 1 / table.getColumnCount())); } table.getColumnModel().getColumn(i).setWidth(newColWidth); table.getColumnModel().getColumn(i).setPreferredWidth(newColWidth); // COMMONS-111 this line of code prevents user resizing of columns ... // table.getColumnModel().getColumn(i).setMinWidth(newColWidth); } } private TreeModel treeModel; private class XulTreeNode extends DefaultMutableTreeNode { XulTreeItem item = null; public XulTreeNode(Object obj, XulTreeItem item) { super(obj); this.item = item; } } private void setupTree() { XulTreeNode topNode = new XulTreeNode("placeholder", null); //$NON-NLS-1$ for (XulComponent c : getRootChildren().getChildNodes()) { XulTreeItem item = (XulTreeItem) c; XulTreeNode node = createNode(item); if (node != null) { topNode.add(node); } } treeModel = new XulTreeModel(topNode); tree.setModel(treeModel); tree.setRootVisible(false); DefaultTreeCellRenderer renderer = new DefaultTreeCellRenderer(); renderer.setLeafIcon(null); renderer.setClosedIcon(null); renderer.setOpenIcon(null); tree.setCellRenderer(renderer); tree.setShowsRootHandles(true); this.setExpanded(this.getExpanded()); } private XulTreeNode createNode(XulTreeItem item) { if (item.getRow().getCell(0) == null) { return null; } XulTreeNode node = new XulTreeNode(item.getRow().getCell(0).getLabel(), item); if (item.getChildNodes().size() > 1) { // has children // TODO: make this more defensive XulTreeChildren children = (XulTreeChildren) item.getChildNodes().get(1); for (XulComponent c : children.getChildNodes()) { XulTreeNode childNode = createNode((XulTreeItem) c); if (childNode != null) { node.add(childNode); } } } return node; } @Override public void replaceChild(XulComponent oldElement, XulComponent newElement) throws XulDomException { // TODO Auto-generated method stub super.replaceChild(oldElement, newElement); } @Override public Object getManagedObject() { return scrollpane; } public int[] getSelectedRows() { // FIXME: A selection followed by removals with no new UI interaction returns the previous // selection even if the row count is now less. if (table != null) { int[] tempSelection = table.getSelectedRows(); List<Integer> clensedSelection = new ArrayList<Integer>(); for (int row : tempSelection) { if (row < table.getModel().getRowCount()) { clensedSelection.add(row); } } // no automatic unboxing :( int[] returnArray = new int[clensedSelection.size()]; int idx = 0; for (int row : clensedSelection) { returnArray[idx] = row; idx++; } return returnArray; } else { int[] vals = tree.getSelectionRows(); if (vals == null) { return new int[] {}; } return vals; } } public int[] getAbsoluteSelectedRows() { // FIXME: A selection followed by removals with no new UI interaction returns the previous // selection even if the row count is now less. if (table != null) { return getSelectedRows(); } else { ArrayList<Boolean> expandedState = new ArrayList<Boolean>(); for (int i = 0; i < tree.getRowCount(); i++) { expandedState.add(tree.isExpanded(i)); } for (int i = tree.getRowCount() - 1; i >= 0; i--) { tree.expandRow(i); } int[] vals = tree.getSelectionRows(); for (int i = 0; i < expandedState.size(); i++) { if (expandedState.get(i)) { tree.expandRow(i); } else { tree.collapseRow(i); } } if (vals == null) { return new int[] {}; } return vals; } } public void addRow(XulTreeRow row) { SwingTreeItem item = new SwingTreeItem(row); getRootChildren().addItem(item); } public Object getData() { return null; } public void setData(Object data) { } public void update() { if (!suppressEvents) { if (table != null) { table.setModel(new XulTableModel(this)); updateColumnModel(); calcColumnWidths(); table.updateUI(); } else { setupTree(); tree.updateUI(); } } } private String extractDynamicColType(Object row, int columnPos) { try { String rawMethod = this.columns.getColumn(columnPos).getColumntypebinding(); StringBuilder methodName = new StringBuilder(); methodName.append("get"); //$NON-NLS-1$ methodName.append(rawMethod.substring(0, 1).toUpperCase()); methodName.append(rawMethod.substring(1)); Method method = row.getClass().getMethod(methodName.toString()); return ((String) method.invoke(row, new Object[] {})).toUpperCase(); } catch (Exception e) { logger.warn("Could not extract column type from binding"); //$NON-NLS-1$ } return "text"; // default //$NON-NLS-1$ } private TableCellRenderer getCellRenderer(final SwingTreeCol col) { return new DefaultTableCellRenderer() { public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { ColumnType colType = col.getColumnType(); if (colType == ColumnType.DYNAMIC) { colType = ColumnType.valueOf(extractDynamicColType(elements.toArray()[row], column)); } final XulTreeCell cell = getRootChildren().getItem(row).getRow().getCell(column); switch (colType) { case CHECKBOX: JCheckBox checkbox = new JCheckBox(); if (value instanceof String) { checkbox.setSelected(((String) value).equalsIgnoreCase("true")); //$NON-NLS-1$ } else if (value instanceof Boolean) { checkbox.setSelected((Boolean) value); } else if (value == null) { checkbox.setSelected(false); } if (isSelected) { checkbox.setBackground(Color.LIGHT_GRAY); } checkbox.setEnabled(!cell.isDisabled()); return checkbox; case COMBOBOX: case EDITABLECOMBOBOX: Vector data; final JComboBox comboBox = new JComboBox(); if (cell == null) { } else { data = (cell.getValue() != null) ? (Vector) cell.getValue() : new Vector(); if (data == null) { logger.debug("SwingTreeCell combobox data is null, passed in value: " + value); //$NON-NLS-1$ if (value instanceof Vector) { data = (Vector) value; } } if (data != null) { comboBox.setModel(new DefaultComboBoxModel(data)); try { comboBox.setSelectedIndex(cell.getSelectedIndex()); } catch (Exception e) { logger.error("error setting selected index on the combobox editor"); //$NON-NLS-1$ } } } if (colType == ColumnType.EDITABLECOMBOBOX) { comboBox.setEditable(true); ((JTextComponent) comboBox.getEditor().getEditorComponent()).setText(cell.getLabel()); } if (isSelected) { comboBox.setBackground(Color.LIGHT_GRAY); } comboBox.setEnabled(!cell.isDisabled()); return comboBox; case CUSTOM: return new CustomCellEditorWrapper(cell, customEditors.get(col.getType())); default: JLabel label = new JLabel((String) value); if (isSelected) { label.setOpaque(true); label.setBackground(Color.LIGHT_GRAY); } return label; } } }; } private TableCellEditor getCellEditor(final SwingTreeCol col) { return new DefaultCellEditor(new JComboBox()) { JComponent control; @Override public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, final int row, final int column) { Component comp; ColumnType colType = col.getColumnType(); if (colType == ColumnType.DYNAMIC) { colType = ColumnType.valueOf(extractDynamicColType(elements.toArray()[row], column)); } final XulTreeCell cell = getRootChildren().getItem(row).getRow().getCell(column); switch (colType) { case CHECKBOX: final JCheckBox checkbox = new JCheckBox(); final JTable tbl = table; checkbox.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event) { SwingTree.this.table.setValueAt(checkbox.isSelected(), row, column); tbl.getCellEditor().stopCellEditing(); } }); control = checkbox; if (value instanceof String) { checkbox.setSelected(((String) value).equalsIgnoreCase("true")); //$NON-NLS-1$ } else if (value instanceof Boolean) { checkbox.setSelected((Boolean) value); } else if (value == null) { checkbox.setSelected(false); } if (isSelected) { checkbox.setBackground(Color.LIGHT_GRAY); } comp = checkbox; checkbox.setEnabled(!cell.isDisabled()); break; case EDITABLECOMBOBOX: case COMBOBOX: Vector val = (value != null && value instanceof Vector) ? (Vector) value : new Vector(); final JComboBox comboBox = new JComboBox(val); if (isSelected) { comboBox.setBackground(Color.LIGHT_GRAY); } if (colType == ColumnType.EDITABLECOMBOBOX) { comboBox.setEditable(true); final JTextComponent textComp = (JTextComponent) comboBox.getEditor().getEditorComponent(); textComp.addKeyListener(new KeyListener() { private String oldValue = ""; //$NON-NLS-1$ public void keyPressed(KeyEvent e) { oldValue = textComp.getText(); } public void keyReleased(KeyEvent e) { if (oldValue != null && !oldValue.equals(textComp.getText())) { SwingTree.this.table.setValueAt(textComp.getText(), row, column); oldValue = textComp.getText(); } else if (oldValue == null) { // AWT error where sometimes the keyReleased is fired before keyPressed. oldValue = textComp.getText(); } else { logger.debug("Special key pressed, ignoring"); //$NON-NLS-1$ } } public void keyTyped(KeyEvent e) { } }); comboBox.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event) { // if(textComp.hasFocus() == false && comboBox.hasFocus()){ SwingTree.logger.debug("Setting ComboBox value from editor: " //$NON-NLS-1$ + comboBox.getSelectedItem() + ", " + row + ", " + column); //$NON-NLS-1$ //$NON-NLS-2$ SwingTree.this.table.setValueAt(comboBox.getSelectedIndex(), row, column); // } } }); } else { comboBox.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event) { SwingTree.logger.debug("Setting ComboBox value from editor: " //$NON-NLS-1$ + comboBox.getSelectedItem() + ", " + row + ", " + column); //$NON-NLS-1$ //$NON-NLS-2$ SwingTree.this.table.setValueAt(comboBox.getSelectedIndex(), row, column); } }); } control = comboBox; comboBox.setEnabled(!cell.isDisabled()); comp = comboBox; break; case LABEL: JLabel lbl = new JLabel(cell.getLabel()); comp = lbl; control = lbl; break; case CUSTOM: return new CustomCellEditorWrapper(cell, customEditors.get(col.getType())); default: final JTextField label = new JTextField((String) value); label.getDocument().addDocumentListener(new DocumentListener() { public void changedUpdate(DocumentEvent arg0) { SwingTree.this.table.setValueAt(label.getText(), row, column); } public void insertUpdate(DocumentEvent arg0) { SwingTree.this.table.setValueAt(label.getText(), row, column); } public void removeUpdate(DocumentEvent arg0) { SwingTree.this.table.setValueAt(label.getText(), row, column); } }); if (isSelected) { label.setOpaque(true); // label.setBackground(Color.LIGHT_GRAY); } control = label; comp = label; label.setEnabled(!cell.isDisabled()); label.setDisabledTextColor(Color.DARK_GRAY); break; } return comp; } @Override public Object getCellEditorValue() { if (control instanceof JCheckBox) { return ((JCheckBox) control).isSelected(); } else if (control instanceof JComboBox) { JComboBox box = (JComboBox) control; if (box.isEditable()) { return ((JTextComponent) box.getEditor().getEditorComponent()).getText(); } else { return box.getSelectedIndex(); } } else if (control instanceof JTextField) { return ((JTextField) control).getText(); } else { return ((JLabel) control).getText(); } } }; } private class XulTreeModel extends DefaultTreeModel { public XulTreeModel(TreeNode root) { super(root); } } private class XulTableModel extends AbstractTableModel { SwingTree tree = null; public XulTableModel(SwingTree tree) { this.tree = tree; } public void update() { } public int getColumnCount() { return this.tree.getColumns().getColumnCount(); } public int getRowCount() { if (this.tree != null) { XulTreeChildren children = this.tree.getRootChildren(); return (children != null) ? children.getItemCount() : 0; } else { return 0; } } public Object getValueAt(int rowIndex, int columnIndex) { XulTreeCol col = tree.getColumns().getColumn(columnIndex); XulTreeCell cell = this.tree.getRootChildren().getItem(rowIndex).getRow().getCell(columnIndex); if (cell == null) { return null; } ColumnType colType = col.getColumnType(); if (colType == ColumnType.DYNAMIC) { colType = ColumnType.valueOf(extractDynamicColType(elements.toArray()[rowIndex], columnIndex)); } try { switch (colType) { case CHECKBOX: if (cell.getValue() != null) { return cell.getValue(); } case COMBOBOX: case EDITABLECOMBOBOX: if (cell.getValue() != null) { return cell.getValue(); } default: return cell.getLabel(); } } catch (Exception e) { logger.error("Error getting value of cell at row:" + rowIndex + " column:" + columnIndex, e); //$NON-NLS-1$ //$NON-NLS-2$ } return null; } @Override public int findColumn(String columnName) { for (int i = 0; i < tree.getColumns().getColumnCount(); i++) { if (tree.getColumns().getColumn(i).getName().toUpperCase().equals(columnName.toUpperCase())) { return i; } } return -1; } @Override public Class<?> getColumnClass(int columnIndex) { switch (tree.getColumns().getColumn(columnIndex).getColumnType()) { case CHECKBOX: return Boolean.class; case COMBOBOX: case EDITABLECOMBOBOX: return Vector.class; default: return String.class; } } @Override public String getColumnName(int column) { return tree.getColumns().getColumn(column).getLabel(); } @Override public boolean isCellEditable(int rowIndex, int columnIndex) { return tree.getColumns().getColumn(columnIndex).isEditable(); } @Override public void setValueAt(Object value, int rowIndex, int columnIndex) { if (onedit != null && !suppressEvents) { SwingUtilities.invokeLater(new Runnable() { public void run() { invoke(onedit, new Object[] { new Integer(table.getSelectedRow()) }); } }); } XulTreeItem row = this.tree.getRootChildren().getItem(rowIndex); if (row == null) { logger.info("Row removed, setVal returning"); //$NON-NLS-1$ return; } XulTreeCell cell = row.getRow().getCell(columnIndex); ColumnType colType = tree.getColumns().getColumn(columnIndex).getColumnType(); if (colType == ColumnType.DYNAMIC) { colType = ColumnType.valueOf(extractDynamicColType(elements.toArray()[rowIndex], columnIndex)); } switch (colType) { case CHECKBOX: cell.setValue((Boolean) value); break; case COMBOBOX: case EDITABLECOMBOBOX: if (value instanceof String) { cell.setLabel((String) value); } else if (((Integer) value) > -1) { cell.setSelectedIndex((Integer) value); cell.setLabel(((Vector) cell.getValue()).get((Integer) value).toString()); } break; default: cell.setLabel((String) value); } } } public void clearSelection() { table.getSelectionModel().clearSelection(); CellEditor ce = table.getCellEditor(); if (ce != null) { ce.stopCellEditing(); } } public void setSelectedRows(int[] rows) { if (isHierarchical) { tree.setSelectionRows(rows); } else { table.clearSelection(); for (int row : rows) { table.changeSelection(row, -1, false, true); } } } public String getOnedit() { return onedit; } public void setOnedit(String onedit) { this.onedit = onedit; } private boolean suppressEvents = false; private Collection elements; private boolean expanded; public <T> void setElements(Collection<T> elements) { suppressEvents = true; this.elements = elements; this.getRootChildren().removeAll(); // active editor needs updating, but won't if still active if (table != null) { CellEditor ce = table.getCellEditor(); if (ce != null) { ce.stopCellEditing(); } } if (elements == null) { if (table != null) { table.updateUI(); } else { tree.updateUI(); } changeSupport.firePropertyChange("selectedRows", null, getSelectedRows()); //$NON-NLS-1$ changeSupport.firePropertyChange("absoluteSelectedRows", null, getAbsoluteSelectedRows()); //$NON-NLS-1$ return; } try { if (table != null) { for (T o : elements) { XulTreeRow row = this.getRootChildren().addNewRow(); for (int x = 0; x < this.getColumns().getChildNodes().size(); x++) { XulComponent col = this.getColumns().getColumn(x); final XulTreeCell cell = (XulTreeCell) getDocument().createElement("treecell"); //$NON-NLS-1$ XulTreeCol column = (XulTreeCol) col; for (InlineBindingExpression exp : ((XulTreeCol) col).getBindingExpressions()) { logger.debug("applying binding expression [" + exp + "] to xul tree cell [" + cell //$NON-NLS-1$//$NON-NLS-2$ + "] and model [" + o + "]"); //$NON-NLS-1$ //$NON-NLS-2$ String colType = column.getType(); if (StringUtils.isEmpty(colType) == false && colType.equalsIgnoreCase("dynamic")) { //$NON-NLS-1$ colType = extractDynamicColType(o, x); } if (colType == null) { if (StringUtils.isNotEmpty(exp.getModelAttr())) { Binding binding = createBinding((XulEventSource) o, exp.getModelAttr(), cell, exp.getXulCompAttr()); if (!this.editable) { binding.setBindingType(Binding.Type.ONE_WAY); } domContainer.addBinding(binding); binding.fireSourceChanged(); } } else if ((colType.equalsIgnoreCase("combobox") //$NON-NLS-1$ || colType.equalsIgnoreCase("editablecombobox")) //$NON-NLS-1$ && column.getCombobinding() != null) { Binding binding = createBinding((XulEventSource) o, column.getCombobinding(), cell, "value"); //$NON-NLS-1$ binding.setBindingType(Binding.Type.ONE_WAY); domContainer.addBinding(binding); binding.fireSourceChanged(); binding = createBinding((XulEventSource) o, ((XulTreeCol) col).getBinding(), cell, "selectedIndex"); //$NON-NLS-1$ binding.setConversion(new BindingConvertor<Object, Integer>() { @Override public Integer sourceToTarget(Object value) { int index = ((Vector) cell.getValue()).indexOf(value); return index > -1 ? index : 0; } @Override public Object targetToSource(Integer value) { return ((Vector) cell.getValue()).get(value); } }); domContainer.addBinding(binding); binding.fireSourceChanged(); if (colType.equalsIgnoreCase("editablecombobox")) { //$NON-NLS-1$ binding = createBinding((XulEventSource) o, exp.getModelAttr(), cell, exp.getXulCompAttr()); if (!this.editable) { binding.setBindingType(Binding.Type.ONE_WAY); } else { binding.setBindingType(Binding.Type.BI_DIRECTIONAL); } domContainer.addBinding(binding); } } else if (colType.equalsIgnoreCase("checkbox")) { //$NON-NLS-1$ if (StringUtils.isNotEmpty(exp.getModelAttr())) { Binding binding = createBinding((XulEventSource) o, exp.getModelAttr(), cell, "value"); //$NON-NLS-1$ if (!this.editable) { binding.setBindingType(Binding.Type.ONE_WAY); } domContainer.addBinding(binding); binding.fireSourceChanged(); } } else if (colType != null && this.customEditors.containsKey(colType)) { Binding binding = createBinding((XulEventSource) o, exp.getModelAttr(), cell, "value"); //$NON-NLS-1$ binding.setBindingType(Binding.Type.BI_DIRECTIONAL); domContainer.addBinding(binding); binding.fireSourceChanged(); } else { if (StringUtils.isNotEmpty(exp.getModelAttr())) { Binding binding = createBinding((XulEventSource) o, exp.getModelAttr(), cell, exp.getXulCompAttr()); if (!this.editable) { binding.setBindingType(Binding.Type.ONE_WAY); } domContainer.addBinding(binding); binding.fireSourceChanged(); } } } if (column.getDisabledbinding() != null) { String prop = column.getDisabledbinding(); Binding bind = createBinding((XulEventSource) o, column.getDisabledbinding(), cell, "disabled"); //$NON-NLS-1$ bind.setBindingType(Binding.Type.ONE_WAY); domContainer.addBinding(bind); bind.fireSourceChanged(); } row.addCell(cell); } } // Setup context menu.. must do this here, as elements may not have been initialized in the layout() method. if (popupListener == null) { // The newItemBinding must be populated to get the context menu - and once you have it, you have the whole // set of menu options (no coded alternatives today). Review this logic when there is a requirement for // fine-grained control of which menu items are available to the table. if (StringUtils.isNotEmpty(newItemBinding)) { if (isCollectionManaged()) { popupListener = new PopupListener(); table.addMouseListener(popupListener); table.getTableHeader().addMouseListener(popupListener); } else { logger.error( "Operations associated with newitembinding attribute not allowed on an unbound collection. Refactor your model " + "to use a managed collection (for an example, see AbstractModelList)."); } } } } else { // tree for (T o : elements) { XulTreeRow row = this.getRootChildren().addNewRow(); addTreeChild(o, row); } } if (table != null) { table.updateUI(); } else { setupTree(); tree.updateUI(); } suppressEvents = false; // treat as a selection change changeSupport.firePropertyChange("selectedRows", null, getSelectedRows()); //$NON-NLS-1$ changeSupport.firePropertyChange("absoluteSelectedRows", null, getAbsoluteSelectedRows()); //$NON-NLS-1$ } catch (XulException e) { logger.error("error adding elements", e); //$NON-NLS-1$ } catch (Exception e) { logger.error("error adding elements", e); //$NON-NLS-1$ } } private <T> void addTreeChild(T element, XulTreeRow row) { try { XulTreeCell cell = (XulTreeCell) getDocument().createElement("treecell"); //$NON-NLS-1$ for (InlineBindingExpression exp : ((XulTreeCol) this.getColumns().getChildNodes().get(0)) .getBindingExpressions()) { logger.debug("applying binding expression [" + exp + "] to xul tree cell [" + cell + "] and model [" //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$ + element + "]"); //$NON-NLS-1$ // Tree Bindings are one-way for now as you cannot edit tree nodes Binding binding = createBinding((XulEventSource) element, exp.getModelAttr(), cell, exp.getXulCompAttr()); binding.setBindingType(Binding.Type.ONE_WAY); domContainer.addBinding(binding); binding.fireSourceChanged(); } row.addCell(cell); // find children String property = ((XulTreeCol) this.getColumns().getChildNodes().get(0)).getChildrenbinding(); property = "get" + (property.substring(0, 1).toUpperCase() + property.substring(1)); //$NON-NLS-1$ Method childrenMethod = null; try { childrenMethod = element.getClass().getMethod(property, new Class[] {}); } catch (NoSuchMethodException e) { logger.debug( "Could not find children binding method for object: " + element.getClass().getSimpleName()); //$NON-NLS-1$ } Collection<T> children = null; if (childrenMethod != null) { children = (Collection<T>) childrenMethod.invoke(element, new Object[] {}); } else if (element instanceof Collection) { children = (Collection<T>) element; } XulTreeChildren treeChildren = null; if (children != null && children.size() > 0) { treeChildren = (XulTreeChildren) getDocument().createElement("treechildren"); //$NON-NLS-1$ row.getParent().addChild(treeChildren); } for (T child : children) { row = treeChildren.addNewRow(); addTreeChild(child, row); } } catch (Exception e) { logger.error("error adding elements", e); //$NON-NLS-1$ } } public <T> Collection<T> getElements() { return this.elements; } private void fireSelectedItem() { this.changeSupport.firePropertyChange("selectedItem", null, getSelectedItem()); //$NON-NLS-1$ } public Object getSelectedItem() { if (this.isHierarchical && this.elements != null) { int[] vals = tree.getSelectionRows(); if (vals == null || vals.length == 0) { return null; } TreePath path = tree.getSelectionPath(); if (path.getLastPathComponent() instanceof XulTreeNode) { XulTreeNode node = (XulTreeNode) path.getLastPathComponent(); // now link node.item to object via bindings SearchBundle b = findSelectedIndex(new SearchBundle(), getRootChildren(), node.item); vals[0] = b.curPos; } String property = ((XulTreeCol) this.getColumns().getChildNodes().get(0)).getChildrenbinding(); property = "get" + (property.substring(0, 1).toUpperCase() + property.substring(1)); //$NON-NLS-1$ // Method childrenMethod = null; // try { // childrenMethod = elements.getClass().getMethod(property, new Class[] {}); // } catch (NoSuchMethodException e) { // // Since this tree is built recursively, when at a leaf it will throw this exception. // logger.debug(e); // return null; // } FindSelectedItemTuple tuple = findSelectedItem(this.elements, property, new FindSelectedItemTuple(vals[0])); return tuple != null ? tuple.selectedItem : null; } else if (!this.isHierarchical() && elements != null && this.getSelectedRows().length > 0 && elements.toArray().length > this.getSelectedRows()[0]) { return elements.toArray()[this.getSelectedRows()[0]]; } return null; } private class SearchBundle { int curPos; boolean found; Object selectedItem; } private SearchBundle findSelectedIndex(SearchBundle bundle, XulTreeChildren children, XulTreeItem selectedItem) { for (XulComponent c : children.getChildNodes()) { if (c == selectedItem) { bundle.found = true; return bundle; } bundle.curPos++; if (c.getChildNodes().size() > 1) { SearchBundle b = findSelectedIndex(bundle, (XulTreeChildren) c.getChildNodes().get(1), selectedItem); if (b.found) { return b; } } } return bundle; } private static class FindSelectedItemTuple { Object selectedItem = null; int curpos = -1; // ignores first element (root) int selectedIndex; public FindSelectedItemTuple(int selectedIndex) { this.selectedIndex = selectedIndex; } } // private FindSelectedItemTuple findSelectedItem(Object parent, Method childrenMethod, FindSelectedItemTuple tuple) { // if (tuple.curpos == tuple.selectedIndex) { // tuple.selectedItem = parent; // return tuple; // } // Collection children = null; // try { // children = (Collection) childrenMethod.invoke(parent, new Object[] {}); // } catch (Exception e) { // logger.error(e); // return null; // } // // if (children == null || children.size() == 0) { // return null; // } // // for (Object child : children) { // tuple.curpos++; // findSelectedItem(child, childrenMethod, tuple); // if (tuple.selectedItem != null) { // return tuple; // } // } // return null; // } private FindSelectedItemTuple findSelectedItem(Object parent, String childrenMethodProperty, FindSelectedItemTuple tuple) { if (tuple.curpos == tuple.selectedIndex) { tuple.selectedItem = parent; return tuple; } Collection children = getChildCollection(parent, childrenMethodProperty); if (children == null || children.size() == 0) { return null; } for (Object child : children) { tuple.curpos++; findSelectedItem(child, childrenMethodProperty, tuple); if (tuple.selectedItem != null) { return tuple; } } return null; } private static Collection getChildCollection(Object obj, String childrenMethodProperty) { Collection children = null; Method childrenMethod = null; try { childrenMethod = obj.getClass().getMethod(childrenMethodProperty, new Class[] {}); } catch (NoSuchMethodException e) { if (obj instanceof Collection) { children = (Collection) obj; } } try { if (childrenMethod != null) { children = (Collection) childrenMethod.invoke(obj, new Object[] {}); } } catch (Exception e) { logger.error(e); return null; } return children; } public void setExpanded(boolean expanded) { this.expanded = expanded; if (this.tree != null) { int rowCount = 0; int newRowCount = 0; while ((newRowCount = tree.getRowCount()) > 0 && newRowCount > rowCount) { rowCount = newRowCount; for (int i = 0; i < rowCount; i++) { tree.expandRow(i); } } } } public boolean getExpanded() { return this.expanded; } public void registerCellEditor(String key, org.pentaho.ui.xul.util.TreeCellEditor editor) { this.customEditors.put(key, editor); } private class CustomCellEditorWrapper extends JLabel implements TreeCellEditorCallback { private org.pentaho.ui.xul.util.TreeCellEditor editor; private XulTreeCell cell; public CustomCellEditorWrapper(XulTreeCell cell, org.pentaho.ui.xul.util.TreeCellEditor editor) { super(); this.editor = editor; this.cell = cell; this.setText(this.cell.getValue().toString()); final int col = cell.getParent().getChildNodes().indexOf(cell); XulTreeItem item = (XulTreeItem) cell.getParent().getParent(); final int row = item.getParent().getChildNodes().indexOf(item); final Object boundObj = (SwingTree.this.getElements() != null) ? SwingTree.this.getElements().toArray()[row] : null; final String columnBinding = SwingTree.this.getColumns().getColumn(col).getBinding(); this.addMouseListener(new MouseAdapter() { @Override public void mousePressed(MouseEvent arg0) { CustomCellEditorWrapper.this.editor.show(row, col, boundObj, columnBinding, CustomCellEditorWrapper.this); } }); } public void onCellEditorClosed(Object value) { this.setText(value.toString()); cell.setValue(value); } } private static class CustomTreeCellEditor implements org.pentaho.ui.xul.util.TreeCellEditor { private TreeCellEditorCallback callback; public Object getValue() { // TODO Auto-generated method stub return null; } public void hide() { // TODO Auto-generated method stub } public void setValue(Object val) { // TODO Auto-generated method stub } public void show(int row, int col, Object boundObj, String columnBinding, TreeCellEditorCallback callback) { this.callback = callback; String returnVal = JOptionPane.showInputDialog("Enter a Value"); //$NON-NLS-1$ this.callback.onCellEditorClosed(returnVal); } } public void registerCellRenderer(String key, TreeCellRenderer renderer) { } public void setBoundObjectExpanded(Object o, boolean expanded) { throw new UnsupportedOperationException("not implemented"); //$NON-NLS-1$ } public void setTreeItemExpanded(XulTreeItem item, boolean expanded) { throw new UnsupportedOperationException("not implemented"); //$NON-NLS-1$ } public void collapseAll() { if (this.isHierarchical) { // TODO: Not yet implemented } } public void expandAll() { if (this.isHierarchical) { this.setExpanded(true); } } public <T> Collection<T> getSelectedItems() { // TODO Auto-generated method stub return null; } public <T> void setSelectedItems(Collection<T> items) { int[] selIndexes = new int[items.size()]; if (this.isHierarchical && this.elements != null) { int pos = 0; for (T t : items) { selIndexes[pos++] = findIndexOfItem(t); } } this.setSelectedRows(selIndexes); } public int findIndexOfItem(Object o) { String property = ((XulTreeCol) this.getColumns().getChildNodes().get(0)).getChildrenbinding(); property = "get" + (property.substring(0, 1).toUpperCase() + property.substring(1)); //$NON-NLS-1$ Method childrenMethod = null; try { childrenMethod = elements.getClass().getMethod(property, new Class[] {}); } catch (NoSuchMethodException e) { // Since this tree is built recursively, when at a leaf it will throw this exception. logger.debug(e); } FindSelectedIndexTuple tuple = findSelectedItem(this.elements, childrenMethod, new FindSelectedIndexTuple(o)); return tuple.selectedIndex; } private static class FindSelectedIndexTuple { Object selectedItem = null; Object currentItem = null; // ignores first element (root) int curpos = -1; // ignores first element (root) int selectedIndex = -1; public FindSelectedIndexTuple(Object selectedItem) { this.selectedItem = selectedItem; } } private FindSelectedIndexTuple findSelectedItem(Object parent, Method childrenMethod, FindSelectedIndexTuple tuple) { if (tuple.selectedItem == tuple.currentItem) { tuple.selectedItem = parent; return tuple; } Collection children = null; try { children = (Collection) childrenMethod.invoke(parent, new Object[] {}); } catch (Exception e) { logger.error(e); return null; } if (children == null || children.size() == 0) { return null; } for (Object child : children) { tuple.curpos++; findSelectedItem(child, childrenMethod, tuple); if (tuple.selectedIndex > -1) { return tuple; } } return null; } public boolean isHiddenrootnode() { // TODO Auto-generated method stub return false; } public void setHiddenrootnode(boolean hidden) { // TODO Auto-generated method stub } public String getCommand() { return command; } public void setCommand(String command) { this.command = command; } public boolean isPreserveexpandedstate() { return false; } public void setPreserveexpandedstate(boolean preserve) { } public boolean isSortable() { // TODO Auto-generated method stub return false; } public void setSortable(boolean sort) { // TODO Auto-generated method stub } public boolean isTreeLines() { // TODO Auto-generated method stub return false; } public void setTreeLines(boolean visible) { // TODO Auto-generated method stub } public void setNewitembinding(String binding) { newItemBinding = binding; } public String getNewitembinding() { return newItemBinding; } public void setAutocreatenewrows(boolean auto) { this.autoCreateNewRows = auto; } public boolean getAutocreatenewrows() { return autoCreateNewRows; } private void insertRowAtLast() { if (this.elements != null && newItemBinding != null) { // Bound table. invoke(newItemBinding); } else if (autoCreateNewRows) { getRootChildren().addNewRow(); update(); } } public boolean isPreserveselection() { // TODO This method is not fully implemented. We need to completely implement this in this class return false; } public void setPreserveselection(boolean preserve) { // TODO This method is not fully implemented. We need to completely implement this in this class } private Binding createBinding(XulEventSource source, String prop1, XulEventSource target, String prop2) { if (bindingProvider != null) { return bindingProvider.getBinding(source, prop1, target, prop2); } return new SwingBinding(source, prop1, target, prop2); } /** * This listener controls the context menu. Only available if the newItemBinding is populated. */ class PopupListener extends MouseAdapter { public void mousePressed(MouseEvent e) { showPopup(e); } public void mouseReleased(MouseEvent e) { showPopup(e); } private void showPopup(MouseEvent e) { if (e.isPopupTrigger()) { int count = getAbsoluteSelectedRows().length; deleteRowMenu.setEnabled(count == 1); deleteRowsMenu.setEnabled(count > 1); keepOnlyRowsMenu.setEnabled(count > 0); popupMenu.show(e.getComponent(), e.getX(), e.getY()); } } } class InsertRowsActionAdapter implements ActionListener { SwingTree adaptee; InsertRowsActionAdapter(SwingTree adaptee) { this.adaptee = adaptee; } public void actionPerformed(ActionEvent e) { insertRowAtLast(); } } class DeleteRowsActionAdapter implements ActionListener { SwingTree adaptee; DeleteRowsActionAdapter(SwingTree adaptee) { this.adaptee = adaptee; } public void actionPerformed(ActionEvent e) { int[] rows = adaptee.getAbsoluteSelectedRows(); // WEAK: any type of unsynchronized sorting on either side, and this will be inaccurate Object[] elementObjects = elements.toArray(); // sort the rows high to low ArrayList<Integer> rowArray = new ArrayList<Integer>(); for (int i = 0; i < rows.length; i++) { rowArray.add(rows[i]); } Collections.sort(rowArray, Collections.reverseOrder()); // remove the items in that order for (int i = 0; i < rowArray.size(); i++) { int item = rowArray.get(i); if (item >= 0 && item < elements.size()) { elements.remove(elementObjects[item]); } } } } class KeepOnlyRowsActionAdapter implements ActionListener { SwingTree adaptee; KeepOnlyRowsActionAdapter(SwingTree adaptee) { this.adaptee = adaptee; } public void actionPerformed(ActionEvent e) { int[] rows = adaptee.getAbsoluteSelectedRows(); // WEAK: any type of unsynchronized sorting on either side, and this will be inaccurate Object[] elementObjects = elements.toArray(); for (int modelIndex = elements.size() - 1; modelIndex >= 0; modelIndex--) { boolean inProcessRows = false; for (int i = 0; i < rows.length; i++) { inProcessRows = modelIndex == rows[i]; if (inProcessRows) { break; } } if (inProcessRows) { // keep } else { elements.remove(elementObjects[modelIndex]); } } } } /** * Certain operations (context menus) are only available to bound collections. Do not expose functions that require a * bound colleciton if the collection is not bound. * * @return true if the collection implements the XulManagedCollection interface */ private boolean isCollectionManaged() { return (elements instanceof XulManagedCollection); } /** * This allows a context menu on an empty table. Without it, the context menu is only available on the row headers. */ class ScrollableJTable extends JTable { public boolean getScrollableTracksViewportHeight() { return getPreferredSize().height < getParent().getHeight(); } } }