/*
* Copyright (c) 2007 BUSINESS OBJECTS SOFTWARE LIMITED
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of Business Objects nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/*
* JDBCResultSetTableHeader.java
* Created: Oct 30, 2003
* By: Kevin Sit
*/
package org.openquark.gems.client.valueentry;
import java.awt.Component;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DragGestureEvent;
import java.awt.dnd.DragGestureListener;
import java.awt.dnd.DragSource;
import java.awt.event.InputEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.swing.JTable;
import javax.swing.UIManager;
import javax.swing.plaf.UIResource;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import org.openquark.gems.client.valueentry.JDBCResultSetEditor.JDBCResultSetDragPointHandler;
/**
* This is the object which manages the header of the table embedded in a
* <code>JDBCResultSetEditor</code>. Users cannot reorder columns in this
* table, only resizing is allowed. This header also allows the user to drag
* a column and drop it onto an external component.
*
* This table header class is designed to work with <code>JDBCResultSetEditor</code> only.
*
* @author ksit
*/
public class JDBCResultSetTableHeader extends JTableHeader {
private static final long serialVersionUID = 8633791832079777878L;
/**
* The drag geature listener for the table header. This listener verifies
* that the gesture event is originated from a mouse event and register
* the drag action with the drag source.
*/
private class JDBCTableHeaderDragGestureListener implements DragGestureListener {
/* (non-Javadoc)
* @see java.awt.dnd.DragGestureListener#dragGestureRecognized(java.awt.dnd.DragGestureEvent)
*/
public void dragGestureRecognized(DragGestureEvent dge) {
// if the user is attempting to resize the column, then don't try to
// start our own drag column event because it will interfere with
// JTable's internal drag and drop mechanism!
if (getResizingColumn() != null) {
return;
}
// no need to continue if the drag event is not triggered by a mouse
if (!(dge.getTriggerEvent() instanceof MouseEvent)) {
return;
}
// don't need to continue if there is no selected column or the
// result set is null
int[] indices = columnModel.getSelectedColumns();
if (indices.length == 0 || parentEditor.getResultAdapter().getResultSet() == null) {
return;
}
// start the drag now and register listeners for mouse drag motions
// if the drag was initiated successfully
dragPointHandler.dragColumns(dge, parentEditor, table.getColumnModel());
}
}
/**
* This mouse listener should be registered with the table in order to revert
* back to row selection mode when a cell is selected.
*/
private class JDBCTableMouseListener extends MouseAdapter {
/* (non-Javadoc)
* @see java.awt.event.MouseListener#mousePressed(java.awt.event.MouseEvent)
*/
@Override
public void mousePressed(MouseEvent e) {
changeToRowSelectionMode();
// resolve the mouse location to a row index
int index = table.rowAtPoint(e.getPoint());
if (index < 0) {
return;
}
table.addRowSelectionInterval(index, index);
}
}
/**
* This mouse listener should be registered with the table header component
* to pick up the Ctrl+Button and Shift+Button drag and drop gesture events.
* When this listener is first called, it will register the JDBC table mouse
* listener with the parent table. This listener also sets the column
* selection mode in the table.
*/
private class JDBCTableHeaderMouseListener extends MouseAdapter {
private boolean selectionEventHandled;
/* (non-Javadoc)
* @see java.awt.event.MouseListener#mousePressed(java.awt.event.MouseEvent)
*/
@Override
public void mousePressed(MouseEvent e) {
// run the common code first
int colIndex = mouseEventOccured(e);
if (colIndex < 0) {
return;
}
// ignore the mouse-pressed event if the column is already in the current selection.
// This is necessary because we don't want to lose the selection if the user
// holds the mouse button down (i.e. starts to drag) without pressing the
// Ctrl key or the Shift key.
selectionEventHandled = false;
if (!table.isColumnSelected(colIndex)) {
if (isMultiSelectGesture(e)) {
handleMultiSelectMouseGesture(e, colIndex);
} else {
handleSingleSelectMouseGesture(e, colIndex);
}
// the flag to the true so that we don't need to handle the selection gesture
// in the mouse release event
selectionEventHandled = true;
}
}
/* (non-Javadoc)
* @see java.awt.event.MouseListener#mouseReleased(java.awt.event.MouseEvent)
*/
@Override
public void mouseReleased(MouseEvent e) {
// run the common code first
int colIndex = mouseEventOccured(e);
if (colIndex < 0) {
return;
}
// if the selection is already handled at the time when the mouse button is
// pressed, then we should not handle it again in here
if (!selectionEventHandled) {
if (isMultiSelectGesture(e)) {
handleMultiSelectMouseGesture(e, colIndex);
} else {
handleSingleSelectMouseGesture(e, colIndex);
}
}
}
/**
* This method is called whenever there is a mouse event occured.
* @param e
* @return int The index of the column that corresponds to the mouse click location.
*/
private int mouseEventOccured(MouseEvent e) {
// register a listener in the parent table, so that we can re-enable
// the row selection mode
if (tableMouseListener == null) {
tableMouseListener = new JDBCTableMouseListener();
table.addMouseListener(tableMouseListener);
}
// resolve the mouse location to a column index
int index = JDBCResultSetTableHeader.this.columnAtPoint(e.getPoint());
if (index < 0) {
return -1;
} else {
// set up the table to accept column selection, otherwise we cannot
// highlight selected columns.
changeToColumnSelectionModel();
return index;
}
}
}
/**
* A table header cell render that supports PLAF and it displays the table
* header value as a tool tip as well. This is useful for showing the name
* of a really long string.
*/
private static class TooltipEnabledCellRenderer extends DefaultTableCellRenderer implements UIResource {
private static final long serialVersionUID = 5308446012366212919L;
/* (non-Javadoc)
* @see javax.swing.table.TableCellRenderer#getTableCellRendererComponent(javax.swing.JTable, java.lang.Object, boolean, boolean, int, int)
*/
@Override
public Component getTableCellRendererComponent(
JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
if (table != null) {
JTableHeader header = table.getTableHeader();
if (header != null) {
setForeground(header.getForeground());
setBackground(header.getBackground());
setFont(header.getFont());
}
}
String str = (value == null ? "" : value.toString());
setText(str);
setToolTipText(str);
setBorder(UIManager.getBorder("TableHeader.cellBorder"));
return this;
}
}
/**
* Parent editor that creates this header.
*/
private final JDBCResultSetEditor parentEditor;
/**
* Drag manager associated with this component.
*/
private final JDBCResultSetDragPointHandler dragPointHandler;
/**
* Drag manager associated with this component.
*/
private MouseListener headerMouseListener;
/**
* Drag manager associated with this component.
*/
private MouseListener tableMouseListener;
/**
* Variables used to keep track of selection range indices. Do NOT modify the values of
* these two variables! Use <code>beginRangeSelection</code> or <code>endRangeSelection</code>
* to manipulate these variables implicitly.
*/
private int rangeBeginIndex = -1;
private int rangeEndIndex = -1;
public JDBCResultSetTableHeader(
TableColumnModel cm,
JDBCResultSetEditor parentEditor,
JDBCResultSetDragPointHandler dragPointHandler) {
super(cm);
this.parentEditor = parentEditor;
this.dragPointHandler = dragPointHandler;
initializeDragAndDrop();
}
/* (non-Javadoc)
* @see javax.swing.table.JTableHeader#setReorderingAllowed(boolean)
*/
@Override
public void setReorderingAllowed(boolean reorderingAllowed) {
// do nothing: users cannot re-enable the resizing option
}
/* (non-Javadoc)
* @see javax.swing.table.JTableHeader#setResizingColumn(javax.swing.table.TableColumn)
*/
@Override
public void setResizingColumn(TableColumn aColumn) {
super.setResizingColumn(aColumn);
if (aColumn != null) {
changeToRowSelectionMode();
}
}
/* (non-Javadoc)
* @see javax.swing.table.JTableHeader#createDefaultRenderer()
*/
@Override
protected TableCellRenderer createDefaultRenderer() {
return new TooltipEnabledCellRenderer();
}
/**
* Initializes the drag and drop system to allow dragging this component and dropping
* it to an external entity.
*/
private void initializeDragAndDrop() {
// in order to support drag and drop, we have to disable reordering because JTable's
// reordering is implemented using a mouse pressed listener, which conflicts with
// the standard drag and drop API
super.setReorderingAllowed(false);
// initialize the component as a drag source
if (dragPointHandler != null) {
// we have to use the COPY_OR_MOVE action because the workspace drop
// target only recognizes the MOVE command, but the default gesture
// listener links the Ctrl+Button1 gesture to the COPY command
DragSource.getDefaultDragSource().createDefaultDragGestureRecognizer(
this,
DnDConstants.ACTION_COPY_OR_MOVE,
new JDBCTableHeaderDragGestureListener());
}
if (headerMouseListener == null) {
headerMouseListener = new JDBCTableHeaderMouseListener();
addMouseListener(headerMouseListener);
}
}
/**
* Returns true if the given mouse event is a mouse gesture that selects multiple
* columns from the table.
* @param e
* @return boolean
*/
private boolean isMultiSelectGesture(MouseEvent e) {
return (e.getModifiers() & (InputEvent.CTRL_MASK | InputEvent.SHIFT_MASK)) > 0;
}
/**
* Performs the selection operation on a single column. Caller is responsible
* for ensuring the validity of the given column index.
* @param e
* @param colIndex
*/
private void handleSingleSelectMouseGesture(MouseEvent e, int colIndex) {
table.clearSelection();
beginRangeSelection(colIndex);
}
/**
* Performs the selection operation on more than one columns. Caller is responsible
* for ensuring the validity of the given column index.
* @param e
* @param colIndex
*/
private void handleMultiSelectMouseGesture(MouseEvent e, int colIndex) {
int modifiers = e.getModifiers();
// the Shift key gesture overrides the Ctrl key: even if both Ctrl and Shift keys
// are pressed, we are still doing a range selection (i.e. multiple ranges).
if ((modifiers & InputEvent.SHIFT_MASK) > 0) {
if (rangeBeginIndex >= 0) {
// this is a special case: the user had already selected a range, however,
// the user did not let go of the Shift key and clicked on another column.
// This action should cause the original selection to be cancelled.
if (rangeEndIndex >= 0) {
table.removeColumnSelectionInterval(rangeBeginIndex, rangeEndIndex);
}
endRangeSelection(colIndex);
} else {
// only invoked when this method is first called: since the begin index
// is not set, then we fall back to single selection mode even if the mouse
// event is a multiselect gesture
handleSingleSelectMouseGesture(e, colIndex);
}
} else if ((modifiers & InputEvent.CTRL_MASK) > 0) {
// if the given index already exists in the selection model, then remove it
// from the selection model. Otherwise, we assume that the user is trying
// to start a new range selection.
if (table.isColumnSelected(colIndex)) {
table.removeColumnSelectionInterval(colIndex, colIndex);
} else {
beginRangeSelection(colIndex);
}
}
}
/**
* Marks the beginning of a range selection. This method attempts to add the
* column, referenced by the index, to the selection model and updates the
* range selection indices.
* @param index
*/
private void beginRangeSelection(int index) {
table.addColumnSelectionInterval(index, index);
rangeBeginIndex = index;
rangeEndIndex = -1;
}
/**
* Marks the end of a range selection. This method attempts to add the range
* of columns to the selection model and updates the range selection indices.
* @param index
*/
private void endRangeSelection(int index) {
if (rangeBeginIndex >= 0) {
table.addColumnSelectionInterval(rangeBeginIndex, index);
rangeEndIndex = index;
}
}
/**
* A convenient method for switching the table to row selection mode. Cannot
* cannot this method in the constructor.
*/
private void changeToRowSelectionMode() {
table.clearSelection();
table.setRowSelectionAllowed(true);
table.getColumnModel().setColumnSelectionAllowed(false);
}
/**
* A convenient method for switching the table to column selection mode. Cannot
* cannot this method in the constructor.
*/
private void changeToColumnSelectionModel() {
table.setRowSelectionAllowed(false);
table.getColumnModel().setColumnSelectionAllowed(true);
}
}