/* * Copyright (C) 2005 David Orme <djo@coconut-palm-software.com> * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * David Orme - Initial API and implementation * Coconut Palm Software, Inc. - API cleanup * Elias Volanakis - 267316 */ package org.eclipse.swt.nebula.widgets.compositetable; import java.lang.reflect.Constructor; import java.util.ArrayList; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import org.eclipse.swt.SWT; import org.eclipse.swt.events.KeyEvent; import org.eclipse.swt.events.KeyListener; import org.eclipse.swt.events.MouseEvent; import org.eclipse.swt.events.MouseListener; import org.eclipse.swt.events.MouseMoveListener; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.widgets.Canvas; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Layout; import org.eclipse.swt.widgets.Menu; /** * Class CompositeTable. n. (1) An SWT virtual table control that extends * Composite. (2) An SWT virtual table control that is composed of many * Composites, each representing a header or a row, one below the other. * <p> * * CompositeTable is designed specifically to work nicely in the Eclipse Visual * Editor, but it is equally easy to use in hand-coded layouts. * <p> * * <b>Synopsis:</b> * <p> * In order to edit anything, one must: * <p> * <ul> * <li>Extend Composite or Canvas and create an object that can be duplicated * to represent the rows in your table. * <li>Optionally, extend Composite or Canvas and create a header object in the * same way. * <li>If the canvas and row objects do not have a layout manager, the * CompositeTable will automatically supply one that lays out child controls in * a visual table. If they have a layout manager, CompositeTable will let them * use that. * <li>Create a CompositeTable object, either using VE or using hand-coded SWT. * <li>Drop the header (if applicable), then the row object on the * CompositeTable or simply write code that creates instances of these objects * in that order as child controls of your CompositeTable. * <li>Set the RunTime property to "true". Your control is now "live." * <li>Add a RowConstructionListener if you need to add event handlers to * individual row controls when a row is created. * <li>Add a RowContentProvider that knows how to put data into your row * object's controls on demand. * <li>Add a RowFocusListener to validate and save changed data. * <li>Set the NumRowsInCollection property to the number of rows in the * underlying data structure. * </ul> * * Detailed description: * <p> * * This control is designed to work inside of the Eclipse Visual Editor. To use * it, drop one on the design surface. (Even though it extends Canvas, it does * not make sense to put a layout manager on it.) * <p> * * Next create one or two new custom controls by using the Visual Editor to * extend Composite. If you create one custom control, it will be used as the * prototype for all rows that will be displayed in the table. If you create * two, the first one will be used as a prototype for the header and the second * one will be used as a prototype for the rows. * <p> * * If these custom controls are not given layout managers (null layout), then * CompositeTable will automatically detect this situation and will supply its * own layout manager that will automatically lay out the children of these * controls in columns to form a table. However, if you supply layout managers * for your header prototype and row prototype objects, CompositeTable will * respect your choice. If you use CompositeTable's built-in layout manager, * then the weights property will be used to determine what percentage of the * total width will be allocated to each column. If this property is not set or * if the sum of their elements does not equal 100, the columns are created as * equal sizes. * <p> * * Once you have created your (optional) Header and Row custom controls, simply * drop them onto your CompositeTable control in VE. The first of these two * custom controls to be instantiated in your code will be interpreted by the * CompositeTable as the header control and the second will be interpreted as * the row control. * <p> * * Now that you have defined the (optional) header and row, you can switch your * CompositeTable into run mode and use it. This is done by switching the * RunTime property to true. * <p> * * Once in run mode, all of the CompositeTable's properties will be active. In * order to use it, set the NumRowsInCollection property to the number of rows * in the collection you want to display. And add a RefreshContentProvider, * which will be called whenever CompositeTable needs to refresh a particular * row. * <p> * * Please refer to the remainder of the JavaDoc for information on the remaining * properties and events. * <p> * * Although this control extends Composite, it is not intended to be subclassed * except within its own implementation and it makes no sense to set a layout * manager on it (although as discussed above, the child controls may have * layout managers). * * @author djo * @since 3.2 */ public class CompositeTable extends Canvas { // Property fields here private boolean runTime = false; private int numRowsInCollection = 0; private int maxRowsVisible = Integer.MAX_VALUE; // Private fields here private Constructor headerConstructor = null; private Control headerControl = null; private Constructor rowConstructor = null; private Control rowControl = null; // NEBULA_TODO: on public API methods that reference contentPane, make sure it's // not null before doing anything private InternalCompositeTable contentPane = null; /** * Constructor CompositeTable. Construct a CompositeTable control. * CompositeTable accepts the same style bits as the SWT Canvas. * * @param parent * The SWT parent control. * @param style * Style bits. These are the same as Canvas */ public CompositeTable(Composite parent, int style) { super(parent, style); setBackground(Display.getCurrent().getSystemColor( SWT.COLOR_LIST_BACKGROUND)); setLayout(new Layout() { protected Point computeSize(Composite composite, int wHint, int hHint, boolean flushCache) { if (headerControl == null && rowControl == null) { return new Point(2, 20); } Point headerSize = new Point(0, 0); if (headerControl != null) { headerSize = headerControl.computeSize(SWT.DEFAULT, SWT.DEFAULT); } Point rowSize = new Point(0, 0); if (rowControl != null) { rowSize = rowControl.computeSize(SWT.DEFAULT, SWT.DEFAULT); } Point result = new Point(Math.max(headerSize.x, rowSize.x), headerSize.y + rowSize.y); return result; } protected void layout(Composite composite, boolean flushCache) { resize(); } }); } /* * (non-Javadoc) * * @see org.eclipse.swt.widgets.Control#setBackground(org.eclipse.swt.graphics.Color) */ public void setBackground(Color color) { super.setBackground(color); if (contentPane != null) { contentPane.setBackground(color); } } private int numChildrenLastTime = 0; /** * (non-API) Method resize. Resize this table's contents. Called from within * the custom layout manager. */ protected final void resize() { if (isRunTime()) { Control[] children = getChildren(); int childrenLength = 0; for (int i = 0; i < children.length; i++) { if (!(children[i] instanceof InternalCompositeTable)) { ++childrenLength; } } if (numChildrenLastTime != childrenLength) { resizeAndRecordPrototypeRows(); showPrototypes(false); contentPane.dispose(); contentPane = new InternalCompositeTable(this, SWT.NULL); } updateVisibleRows(); } else { resizeAndRecordPrototypeRows(); } } /** * (non-API) Method updateVisibleRows. Makes sure that the content pane is * displaying the correct number of visible rows given the control's size. * Called from within #resize. */ protected void updateVisibleRows() { if (contentPane == null) { switchToRunMode(); } Point size = getSize(); contentPane.setBounds(0, 0, size.x, size.y); } /** * Switch from design mode where prototype header/row objects can be dropped * on the control into run mode where all of the properties do what you * would expect. */ private void switchToRunMode() { showPrototypes(false); contentPane = new InternalCompositeTable(this, SWT.NULL); } /** * Switch back to design mode so that the prototype header/row objects may * be manipulated directly in a GUI design tool. */ private void switchToDesignMode() { contentPane.dispose(); contentPane = null; showPrototypes(true); resizeAndRecordPrototypeRows(); } /** * Turns display of the prototype objects on or off. * * @param newValue * true of the prototype objects should be displayed; false * otherwise. */ private void showPrototypes(boolean newValue) { if (headerControl != null) { headerControl.setVisible(newValue); } if (rowControl != null) { rowControl.setVisible(newValue); } } /** * (non-API) Method resizeAndRecordPrototypeRows. Figure out what child * controls are the header and row prototype rows respectively and resize * them so they occupy the entire width and their preferred height. */ protected void resizeAndRecordPrototypeRows() { Control[] children = getChildren(); Control[] finalChildren = children; finalChildren = findPrototypeHeaderAndRowObjects(children); headerConstructor = null; headerControl = null; rowConstructor = null; rowControl = null; if (finalChildren.length == 0) { return; } findPrototypeConstructors(finalChildren); resizePrototypeObjects(finalChildren); numChildrenLastTime = children.length; // I think this is a hack to work around some SWT layout bug, but // I'm not sure so I'm going to comment it out and see if that breaks // anything. // Display.getCurrent().asyncExec(new Runnable() { // public void run() { // if (!CompositeTable.this.isDisposed()) { // if (!getParent().isDisposed()) { // getParent().layout(true); // } // } // } // }); } private Control[] findPrototypeHeaderAndRowObjects(Control[] children) { Control[] finalChildren; // Find first two prototypes ArrayList realChildren = new ArrayList(); for (int i = 0; i < children.length; i++) { if (children[i] instanceof InternalCompositeTable) { continue; } if (realChildren.size() < 2) { realChildren.add(children[i]); } } finalChildren = (Control[]) realChildren .toArray(new Control[realChildren.size()]); return finalChildren; } private void findPrototypeConstructors(Control[] finalChildren) { // Get a constructor for the header and/or the row prototype if (finalChildren.length == 1) { try { rowControl = (Composite) finalChildren[0]; rowConstructor = finalChildren[0].getClass().getConstructor( new Class[] { Composite.class, Integer.TYPE }); } catch (Exception e) { throw new RuntimeException( "Unable to get constructor object for header or row", e); } } else { try { headerConstructor = finalChildren[0].getClass().getConstructor( new Class[] { Composite.class, Integer.TYPE }); headerControl = finalChildren[0]; rowConstructor = finalChildren[1].getClass().getConstructor( new Class[] { Composite.class, Integer.TYPE }); rowControl = finalChildren[1]; } catch (Exception e) { throw new RuntimeException( "Unable to get constructor object for header or row", e); } } } private void resizePrototypeObjects(Control[] finalChildren) { // Now actually resize the children int top = 0; int width = getSize().x; for (int i = 0; i < finalChildren.length; ++i) { Control control = finalChildren[i]; int height = control.computeSize(SWT.DEFAULT, SWT.DEFAULT).y; control.setBounds(0, top, width, height); top += height; } } /** * Method isRunTime. Returns if the CompositeTable is in run time mode as * opposed to design time mode. In design time mode, the only permitted * operations are to add or remove child Composites to be used as the header * and/or row prototype objects. * * @return true if this CompositeTable is in run mode. false otherwise. */ public boolean isRunTime() { return runTime; } /** * Method setRunTime. Turns run-time mode on or off. When run-time mode is * off, CompositeTable ignores most property operations and will accept * prototype child controls to be added. When run-time mode is on, the * prototype controls are interpreted and all properties become active. * * @param runTime * true if run-time mode should be enabled; false otherwise. */ public void setRunTime(boolean runTime) { if (this.runTime != runTime) { this.runTime = runTime; if (runTime) { if (rowControl == null) { resizeAndRecordPrototypeRows(); } switchToRunMode(); } else { switchToDesignMode(); } } } boolean linesVisible = true; /** * Method getLinesVisible. Returns if the CompositeTable will draw grid * lines on the header and row Composite objects. This property is ignored * if the programmer has set a layout manager on the header and/or the row * prototype objects. * * @return true if the CompositeTable will draw grid lines; false otherwise. */ public boolean getLinesVisible() { return linesVisible; } /** * Method setLinesVisible. Sets if the CompositeTable will draw grid lines * on the header and row Composite objects. This property is ignored if the * programmer has set a layout manager on the header and/or the row * prototype objects. * * @param linesVisible * true if the CompositeTable will draw grid lines; false * otherwise. */ public void setLinesVisible(boolean linesVisible) { this.linesVisible = linesVisible; } String insertHint = "Press <Ctrl-INSERT> to insert new data."; //$NON-NLS-1$ /** * Returns the hint string that will be displayed when there are no rows in * the table. * * @return The hint string that will be displayed when there are no rows in * the table. */ public String getInsertHint() { return insertHint; } /** * Sets the hint string that will be displayed when there are no rows in the * table. The default value is "Press <INS> to insert a new row." * * @param newHint */ public void setInsertHint(String newHint) { this.insertHint = newHint; if (contentPane != null && numRowsInCollection < 1) { contentPane.emptyTablePlaceholder.setMessage(newHint); } } /** * Method getMaxRowsVisible. Returns the maximum number of rows that will be * permitted in the table at once. For example, setting this property to 1 * will have the effect of creating a single editing area with a scroll bar * on the right allowing the user to scroll through all rows using either * the mouse or the PgUp/PgDn keys. The default value is Integer.MAX_VALUE. * * @return the maximum number of rows that are permitted to be visible at * one time, regardless of the control's size. */ public int getMaxRowsVisible() { return maxRowsVisible; } /** * Method setMaxRowsVisible. Sets the maximum number of rows that will be * permitted in the table at once. For example, setting this property to 1 * will have the effect of creating a single editing area with a scroll bar * on the right allowing the user to scroll through all rows using either * the mouse or the PgUp/PgDn keys. The default value is Integer.MAX_VALUE. * * @param maxRowsVisible * the maximum number of rows that are permitted to be visible at * one time, regardless of the control's size. */ public void setMaxRowsVisible(int maxRowsVisible) { this.maxRowsVisible = maxRowsVisible; if (contentPane != null) { contentPane.setMaxRowsVisible(maxRowsVisible); } } /** * Method getNumRowsVisible. Returns the actual number of rows that are * currently visible. Normally CompositeTable displays as many rows as will * fit vertically given the control's size. This value can be clamped to a * maximum using the MaxRowsVisible property. * * @return the actual number of rows that are currently visible. */ public int getNumRowsVisible() { if (contentPane != null) return contentPane.getNumRowsVisible(); return -1; } /** * Method getNumRowsInCollection. Returns the number of rows in the data * structure that is being edited. * * @return the number of rows in the underlying data structure. */ public int getNumRowsInCollection() { return numRowsInCollection; } /** * Method setNumRowsInCollection. Sets the number of rows in the data * structure that is being edited. * * @param numRowsInCollection * the number of rows represented by the underlying data * structure. */ public void setNumRowsInCollection(int numRowsInCollection) { if (rowControl == null) { resizeAndRecordPrototypeRows(); if (contentPane != null) { setRunTime(false); setRunTime(true); } } this.numRowsInCollection = numRowsInCollection; if (contentPane != null) { contentPane.setNumRowsInCollection(numRowsInCollection); } } private int topRow = 0; /** * Method getTopRow. Return the number of the line that is being displayed * in the top row of the CompositeTable editor (0-based). * * @return the number of the top line. */ public int getTopRow() { if (contentPane != null) { return contentPane.getTopRow(); } return topRow; } /** * Method setTopRow. Set the number of the line that is being displayed in * the top row of the CompositeTable editor (0-based). If the new top row is * not equal to the current top row, the table will automatically be * scrolled to the new position. This number must be greater than 0 and less * than NumRowsInCollection. * * @param topRow * the line number of the new top row. */ public void setTopRow(int topRow) { if (topRow < 0 || topRow > numRowsInCollection - 1) { throw new IllegalArgumentException("topRow outside legal range!"); } if (topRow + getNumRowsVisible() - 1 > numRowsInCollection) { topRow = numRowsInCollection - getNumRowsVisible(); } this.topRow = topRow; if (contentPane != null) { contentPane.setTopRow(topRow); } } /** * Makes sure that the focused row is visible * * @return true if the display needed to be scrolled; false otherwise */ public boolean doMakeFocusedRowVisible() { if (contentPane != null) { return contentPane.doMakeFocusedRowVisible(); } return false; } /** * Method doFocusInitialRow. Set the focus on row 0 in the collection. */ public void doFocusInitialRow() { if (contentPane == null) { return; } contentPane.doFocusInitialRow(); } /** * Method doFocusLastRow. Set the focus on row collection.size()-1. */ public void doFocusLastRow() { if (contentPane == null) { return; } contentPane.doFocusLastRow(); } /** * Method doPageUp. Move the focus one page earlier in the collection. * The page size is normally the same as getNumeRowsVisible(). */ public void doPageUp() { if (contentPane == null) { return; } contentPane.doPageUp(); } /** * Method doPageDown. Move the focus one page later in the collection. * The page size is normally the same as getNumeRowsVisible(). */ public void doPageDown() { if (contentPane == null) { return; } contentPane.doPageDown(); } /** * Method doRowUp. Move the focus one row earlier in the collection if * it is not already on row 0. */ public void doRowUp() { if (contentPane == null) { return; } contentPane.doRowUp(); } /** * Method doRowDown. */ public void doRowDown() { if (contentPane == null) { return; } contentPane.doRowDown(); } public boolean doInsertRow() { if (contentPane == null) { return false; } return contentPane.doInsertRow(); } public boolean doDeleteRow() { if (contentPane == null) { return false; } return contentPane.doDeleteRow(); } /** * Method refreshAllRows. Refresh all visible rows in the CompositeTable * from the original data. */ public void refreshAllRows() { if (contentPane != null) { contentPane.updateVisibleRows(); contentPane.refreshAllRows(); } } /** * Method refreshRow. Refreshes a row in the CompositeTable * from the original data. Row is an offset from the * top of the table window. In order to get the current row in the * underlying data structure, compute getTopRow() + getCurrentRow(). */ public void refreshRow(int row) { if (contentPane != null) { // contentPane.updateVisibleRows(); contentPane.refreshRow(row); } } /** * Method getCurrentColumn. Returns the column number of the * currently-focused column (0-based). * * @return the column number of the currently-focused column. */ public int getCurrentColumn() { if (contentPane == null) { return -1; } return getSelection().x; } /** * Method setCurrentColumn. Sets the column number of the currently-focused * column (0-based). * * @param column * The new column to focus. */ public void setCurrentColumn(int column) { setSelection(column, getCurrentRow()); } /** * Method getCurrentRow. Returns the current row number as an offset from * the top of the table window. In order to get the current row in the * underlying data structure, compute getTopRow() + getCurrentRow(). * * @return the current row number as an offset from the top of the table * window. */ public int getCurrentRow() { if (contentPane == null) { return -1; } return getSelection().y; } /** * Method setCurrentRow. Sets the current row number as an offset from the * top of the table window. In order to get the current row in the * underlying data structure, compute getTopRow() + getCurrentRow(). * * @param row * the current row number as an offset from the top of the table * window. */ public void setCurrentRow(int row) { setSelection(getCurrentColumn(), row); } /** * Method getCurrentRowControl. Returns the SWT control that displays the * current row. * * @return Control the current row control. */ public Control getCurrentRowControl() { return contentPane.getCurrentRowControl(); } /** * Method getRowControls. Returns an array of SWT controls where each * control represents a row control in the CompositeTable's current scrolled * position. If CompositeTable is resized, scrolled, such that the rows that * the CompositeTable control is displaying change in any way, the array * that is returned by this method will become out of date and need to be * retrieved again. * * @return Control[] An array of SWT Control objects, each representing an * SWT row object. */ public Control[] getRowControls() { return contentPane.getRowControls(); } /** * Returns the actual header control (not the prototype). * * @return a control instance or null if no header is available */ public Control getHeader() { return (contentPane != null) ? contentPane.getHeader() : null; } /* * (non-Javadoc) * * @see org.eclipse.swt.widgets.Control#setMenu(org.eclipse.swt.widgets.Menu) */ public void setMenu(Menu menu) { super.setMenu(menu); if (contentPane != null) { contentPane.setMenu(menu); } } /** * Method getControlRow. Given a row control, returns its row number * relative to the topRow. * * @param rowControl * The row object to find * @return The row number of the rowControl relative to the topRow (0-based) * @throws IllegalArgumentException * if rowControl is not currently visible */ public int getControlRow(Control rowControl) { return contentPane.getControlRow(rowControl); } /** * Method getSelection. Returns the currently-selected (column, row) pair * where the row specifies the offset from the top of the table window. In * order to get the current row in the underlying data structure, use * getSelection().y + getTopRow(). * * @return the currently-selected (column, row) pair where the row specifies * the offset from the top of the table window, or null if no * selection is available */ public Point getSelection() { if (contentPane == null) { return null; } return contentPane.getSelection(); } /** * Method setSelection. Sets the currently-selected (column, row) pair where * the row specifies the offset from the top of the table window. In order * to get the current row in the underlying data structure, use * getSelection().y + getCurrentRow(). * * @param selection * the (column, row) to select */ public void setSelection(Point selection) { setSelection(selection.x, selection.y); } /** * Method setSelection. Sets the currently-selected (column, row) pair where * the row specifies the offset from the top of the table window. In order * to get the current row in the underlying data structure, use * getSelection().y + getCurrentRow(). * * @param column * the column to select * @param row * the row to select as an offset from the top of the window */ public void setSelection(int column, int row) { if (contentPane == null) { return; } contentPane.setSelection(column, row); } /** * Method clearSelection. Deselects the currently-selected (column, row) pair. */ public void clearSelection() { if (contentPane != null) { contentPane.clearSelection(); } } /** * (non-API) Method getHeaderConstructor. Returns the Constructor object * used internally to construct the table's header or null if there is none. * * @return the header's constructor. */ public Constructor getHeaderConstructor() { return headerConstructor; } /** * (non-API) Method getRowConstructor. Returns the Constructor object used * internally to construct each row object. * * @return the rows' constructor */ public Constructor getRowConstructor() { return rowConstructor; } /** * (non-API) Method getHeaderControl. Returns the prototype header control. * * @return the prototype header control. */ public Control getHeaderControl() { return headerControl; } /** * (non-API) Method getRowControl. Returns the prototype row control. * * @return the prototype row control. */ public Control getRowControl() { return rowControl; } LinkedList contentProviders = new LinkedList(); /** * Method addRowContentProvider. Adds the specified content provider to the * list of content providers that will be called when a row needs to be * filled with data. Most of the time it only makes sense to add a single * one. * * @param contentProvider * The content provider to add. */ public void addRowContentProvider(IRowContentProvider contentProvider) { contentProviders.add(contentProvider); } /** * Method removeRowContentProvider. Removes the specified content provider * from the list of content providers that will be called when a row needs * to be filled with data. * * @param contentProvider * The content provider to remove. */ public void removeRowContentProvider(IRowContentProvider contentProvider) { contentProviders.remove(contentProvider); } LinkedList rowFocusListeners = new LinkedList(); /** * Method addRowListener. Adds the specified listener to the set of * listeners that will be notified when the user wishes to leave a row and * when the user has already left a row. If any listener vetos leaving a * row, the focus remains in the row. * * @param rowListener * The listener to add. */ public void addRowFocusListener(IRowFocusListener rowListener) { rowFocusListeners.add(rowListener); } /** * Method removeRowListener. Removes the specified listener from the set of * listeners that will be notified when the user wishes to leave a row and * when the user has already left a row. If any listener vetos leaving a * row, the focus remains in the row. * * @param listener * The listener to remove. */ public void removeRowFocusListener(IRowFocusListener listener) { rowFocusListeners.remove(listener); } LinkedList insertHandlers = new LinkedList(); /** * Method addInsertHandler. Adds the specified insertHandler to the set of * objects that will be used to handle insert requests. * * @param insertHandler * the insertHandler to add. */ public void addInsertHandler(IInsertHandler insertHandler) { insertHandlers.add(insertHandler); } /** * Method removeInsertHandler. Removes the specified insertHandler from the * set of objects that will be used to handle insert requests. * * @param insertHandler * the insertHandler to remove. */ public void removeInsertHandler(IInsertHandler insertHandler) { insertHandlers.remove(insertHandler); } LinkedList deleteHandlers = new LinkedList(); /** * Method addDeleteHandler. Adds the specified deleteHandler to the set of * objects that will be used to handle delete requests. * * @param deleteHandler * the deleteHandler to add. */ public void addDeleteHandler(IDeleteHandler deleteHandler) { deleteHandlers.add(deleteHandler); } /** * Method removeDeleteHandler. Removes the specified deleteHandler from the * set of objects that will be used to handle delete requests. * * @param deleteHandler * the deleteHandler to remove. */ public void removeDeleteHandler(IDeleteHandler deleteHandler) { deleteHandlers.remove(deleteHandler); } LinkedList rowConstructionListeners = new LinkedList(); /** * Method addRowConstructionListener. Adds the specified * rowConstructionListener to the set of objects that will be used to listen * to row construction events. * * @param rowConstructionListener * the rowConstructionListener to add. */ public void addRowConstructionListener( RowConstructionListener rowConstructionListener) { rowConstructionListeners.add(rowConstructionListener); } /** * Method removeRowConstructionListener. Removes the specified * rowConstructionListener from the set of objects that will be used to * listen to row construction events. * * @param rowConstructionListener * the rowConstructionListener to remove. */ public void removeRowConstructionListener( RowConstructionListener rowConstructionListener) { rowConstructionListeners.remove(rowConstructionListener); } boolean deleteEnabled = true; /** * Method isDeleteEnabled. Returns if delete is enabled. Deletions are only * processed if the DeleteEnabled property is true and at least one delete * handler has been registered. * <p> * * The default value is true. * * @return true if delete is enabled. false otherwise. */ public boolean isDeleteEnabled() { return deleteEnabled; } /** * Method setDeleteEnabled. Sets if delete is enabled. Deletions are only * processed if the DeleteEnabled property is true and at least one delete * handler has been registered. * <p> * * The default value is true. * * @param deleteEnabled * true if delete should be enabled. false otherwise. */ public void setDeleteEnabled(boolean deleteEnabled) { this.deleteEnabled = deleteEnabled; } LinkedList scrollListeners = new LinkedList(); /** * Method addScrollListener. Adds the specified scroll listener to the list * of listeners that will be notified when this CompositeTable control * scrolls the top-visible row. This event is not fired when the * CompositeTable is resized. * * @param scrollListener * the ScrollListener to add. */ public void addScrollListener(ScrollListener scrollListener) { scrollListeners.add(scrollListener); } /** * Method removeScrollListener. Removes the specified scroll listener from * the list of listeners that will be notified when this CompositeTable * control scrolls the top-visible row. * * @param scrollListener * the ScrollListener to remove. */ public void removeScrollListener(ScrollListener scrollListener) { scrollListeners.remove(scrollListener); } private boolean traverseOnTabsEnabled = true; /** * Method isTraverseOnTabsEnabled. Returns true if Tab and Shift-tab cause * the focus to wrap from the end of the table back to the beginning and * Enter causes the focus to advance. Returns false otherwise. * <p> * This property defaults to true. * * @return true if CompositeTable is handling Tab, Shift-tab, and Enter key * behavior; false otherwise. */ public boolean isTraverseOnTabsEnabled() { return traverseOnTabsEnabled; } /** * Method setTraverseOnTabsEnabled. Sets if Tab and Shift-tab cause the * focus to wrap from the end of the table back to the beginning and Enter * causes the focus to advance. * <p> * This property defaults to true. * * @param enabled * true if CompositeTable is handling Tab, Shift-tab, and Enter * key behavior; false otherwise. */ public void setTraverseOnTabsEnabled(boolean enabled) { this.traverseOnTabsEnabled = enabled; } private List keyEventListeners = new ArrayList(); /** * {@inheritDoc} */ public void addKeyListener(KeyListener listener) { keyEventListeners.add(listener); } /** * {@inheritDoc} */ public void removeKeyListener(KeyListener listener) { keyEventListeners.remove(listener); } void fireKeyPressed(KeyEvent e) { for (Iterator i = keyEventListeners.iterator(); i.hasNext();) { KeyListener l = (KeyListener) i.next(); l.keyPressed(e); } } void fireKeyReleased(KeyEvent e) { for (Iterator i = keyEventListeners.iterator(); i.hasNext();) { KeyListener l = (KeyListener) i.next(); l.keyReleased(e); } } private List mouseListeners = new ArrayList(); /** * {@inheritDoc} */ public void addMouseListener(MouseListener listener) { mouseListeners.add(listener); } /** * {@inheritDoc} */ public void removeMouseListener(MouseListener listener) { mouseListeners.remove(listener); } void fireMouseDown(MouseEvent e) { for (Iterator i = mouseListeners.iterator(); i.hasNext();) { MouseListener l = (MouseListener) i.next(); l.mouseDown(e); } } void fireMouseUp(MouseEvent e) { for (Iterator i = mouseListeners.iterator(); i.hasNext();) { MouseListener l = (MouseListener) i.next(); l.mouseUp(e); } } void fireMouseDouble(MouseEvent e) { for (Iterator i = mouseListeners.iterator(); i.hasNext();) { MouseListener l = (MouseListener) i.next(); l.mouseDoubleClick(e); } } private List mouseMoveListeners = new ArrayList(); public void addMouseMoveListener(MouseMoveListener listener) { mouseMoveListeners.add(listener); } public void removeMouseMoveListener(MouseMoveListener listener) { mouseMoveListeners.remove(listener); } void fireMouseMove(MouseEvent e) { for (Iterator i = mouseMoveListeners.iterator(); i.hasNext();) { MouseMoveListener l = (MouseMoveListener) i.next(); l.mouseMove(e); } } } // @jve:decl-index=0:visual-constraint="10,10"