/*
* Copyright 2003-2010 Tufts University Licensed under the
* Educational Community License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may
* obtain a copy of the License at
*
* http://www.osedu.org/licenses/ECL-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an "AS IS"
* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package tufts.vue.ui;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Font;
import java.awt.Point;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DropTarget;
import java.awt.dnd.DropTargetDragEvent;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.dnd.DropTargetEvent;
import java.awt.event.ActionEvent;
import java.awt.event.MouseEvent;
import java.util.Vector;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JLabel;
import javax.swing.JTable;
import javax.swing.ListSelectionModel;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import tufts.vue.DRBrowser;
import tufts.vue.MouseAdapter;
import tufts.vue.VueResources;
import tufts.vue.EventHandler;
import tufts.vue.gui.GUI;
import tufts.vue.gui.Widget;
import tufts.vue.LWComponent;
import tufts.vue.ds.Association;
import tufts.vue.ds.Field;
public class AssociationsPane extends Widget
{
static final long serialVersionUID = 1;
private static final org.apache.log4j.Logger
Log = org.apache.log4j.Logger.getLogger(AssociationsPane.class);
static final int BUTTON_WIDTH = 20;
static final String CHOOSE_FIELD = VueResources.getString("associationsPane.chooseField");
// static AbstractAction addAssociationAction = null;
AbstractAction deleteAssociationAction = null;
JTable associationsTable = null;
BlankRenderer blankRenderer = new BlankRenderer();
GrayRenderer grayRenderer = new GrayRenderer();
public AssociationsPane() {
this(VueResources.getString("associationsPane.name"));
}
public AssociationsPane(String name) {
super(name);
try {
// addAssociationAction = new AbstractAction(VueResources.getString("associationsPane.addassociation")) {
// public void actionPerformed(ActionEvent e) {
// addAssociation();
// }
// };
deleteAssociationAction = new AbstractAction(VueResources.getString("associationsPane.deleteassociation")) {
static final long serialVersionUID = 1;
public void actionPerformed(ActionEvent e) {
deleteAssociation();
}
};
associationsTable = new JTable(new AssociationsTableModel()){
static final long serialVersionUID = 1;
public TableCellRenderer getCellRenderer(int row, int column) {
int lastRow = associationsTable.getRowCount() - 1;
return (row != lastRow) ? super.getCellRenderer(row, column) :
(column != 0) ? grayRenderer : blankRenderer;
}
};
TableColumnModel colModel = associationsTable.getColumnModel();
TableColumn column = colModel.getColumn(0);
column.setPreferredWidth(BUTTON_WIDTH);
column.setMaxWidth(BUTTON_WIDTH);
column.setMinWidth(BUTTON_WIDTH);
column = colModel.getColumn(2);
column.setPreferredWidth(BUTTON_WIDTH);
column.setMaxWidth(BUTTON_WIDTH);
column.setMinWidth(BUTTON_WIDTH);
associationsTable.setDropTarget(new AssociationsDropTarget());
associationsTable.addMouseListener(new AssociationsMouseListener());
associationsTable.getSelectionModel().addListSelectionListener(new AssociationsListSelectionListener());
Font tableFont = GUI.LabelFace;
associationsTable.setFont(tableFont);
associationsTable.setRowHeight((int)(1.5 * associationsTable.getFontMetrics(tableFont).getHeight()));
associationsTable.setShowGrid(false);
setLayout(new BorderLayout());
add(associationsTable);
} catch (Exception ex) {
Log.error(ex);
}
}
public void finalize() {
// addAssociationAction = null;
deleteAssociationAction = null;
associationsTable = null;
}
public void setActions() {
// setMiscAction(this, new AddAssociationListener(), "dockWindow.addButton");
setHelpAction(this, VueResources.getString("dockWindow.Datasources.associationsPane.helpText"));;
enableMenuActions();
}
public void enableMenuActions() {
int deleteCount = associationsTable.getSelectedRowCount(),
deleteRow = associationsTable.getSelectedRow();
// Enable delete for multiple rows or for a single row other than the last row
// (the last row is the the "drop here" row, which can't be deleted).
deleteAssociationAction.setEnabled(deleteCount > 1 || (deleteCount == 1 && deleteRow != Association.getCount()));
setMenuActions(this,
new Action[] {
// addAssociationAction,
deleteAssociationAction
});
}
// public void addAssociation() {
// AssociationsTableModel model = ((AssociationsTableModel)associationsTable.getModel());
// ListSelectionModel selModel = associationsTable.getSelectionModel();
// int insertAt = associationsTable.getSelectedRow();
// // Add the new row above the first selected row;
// // if no row is selected, add it at the end.
// if (insertAt == -1) {
// insertAt = model.getRowCount();
// }
// model.addEmptySlot(insertAt);
// selModel.clearSelection();
// selModel.setSelectionInterval(insertAt, insertAt);
// }
public void deleteAssociation() {
AssociationsTableModel model = ((AssociationsTableModel)associationsTable.getModel());
int toDelete[] = associationsTable.getSelectedRows(),
deleteCount = toDelete.length,
lastRow = associationsTable.getRowCount() - 1;
while (deleteCount > 0) {
deleteCount--;
int deleteRow = toDelete[deleteCount];
if (deleteRow < lastRow) {
model.deleteAssociation(deleteRow);
}
}
}
public boolean dropAssociation(Transferable transfer, int row, int column) {
boolean result = false;
if (column == 1 || column == 3) {
try {
final LWComponent dragNode = tufts.vue.MapDropTarget.extractData
(transfer,
LWComponent.DataFlavor,
LWComponent.class);
final Field field = dragNode.getClientData(tufts.vue.ds.Field.class);
associationsTable.setValueAt(field, row, column);
result = true;
} catch (Throwable t) {
Log.error("exception processing drop " + transfer + " at " + row + "," + column, t);
}
}
return result;
}
public void toggleAssociation() {
if (associationsTable.getSelectedRowCount() == 1 &&
associationsTable.getSelectedColumnCount() == 1 &&
associationsTable.getSelectedColumn() == 0) {
int selectedRow = associationsTable.getSelectedRow();
AssociationsTableModel model = ((AssociationsTableModel)associationsTable.getModel());
model.toggleAssociation(selectedRow);
}
}
protected class AssociationsTableModel extends AbstractTableModel implements Association.Listener
{
static final long serialVersionUID = 1;
private static final int COL_ENABLED = 0;
private static final int COL_FIELD_LEFT = 1;
private static final int COL_EQUALS = 2;
private static final int COL_FIELD_RIGHT = 3;
private Field tmpField0;
private Field tmpField1;
private AssociationsTableModel() {
EventHandler.addListener(Association.Event.class, this);
}
public void eventRaised(Association.Event e) {
fireTableDataChanged();
}
public int getRowCount() {
return Association.getCount() + 1;
}
public int getColumnCount() {
return 4;
}
public boolean isCellEditable(int row, int column) {
return (column == 0);
}
public Class getColumnClass(int column) {
if (getRowCount() > 0)
return getValueAt(0, column).getClass();
else
return null;
}
public Object getValueAt(int row, int column) {
try {
return fetchValue(row, column);
} catch (Throwable t) {
Log.error("failed to fetch value at row=" + row + ", col=" + column, t);
return null;
}
}
private Object fetchValue(final int row, final int column) {
Object result = null;
final int lastRow = Association.getCount();
switch (column) {
case COL_ENABLED:
result = (row == lastRow ? Boolean.FALSE : (Association.get(row).isEnabled() ? Boolean.TRUE : Boolean.FALSE));
break;
case COL_FIELD_LEFT:
result = (row == lastRow ? tmpField0 : Association.get(row).getLeft());
break;
case COL_EQUALS:
result = "=";
break;
case COL_FIELD_RIGHT:
result = (row == lastRow ? tmpField1 : Association.get(row).getRight());
break;
}
if (result == null) {
result = CHOOSE_FIELD;
}
return result;
}
public void setValueAt(Object obj, int row, int column) {
// note: this ignores row for now -- we only pay attention to column
// the only row that can be updated this way is the last row
switch (column) {
case COL_FIELD_LEFT:
if (tmpField1 != obj) {
Log.debug("set f0 to " + obj);
tmpField0 = (Field) obj;
}
break;
case COL_FIELD_RIGHT:
if (tmpField0 != obj) {
Log.debug("set f1 to " + obj);
tmpField1 = (Field) obj;
}
break;
default:
return;
}
Log.debug("f0=" + tmpField0 + "; f1=" + tmpField1);
if (tmpField0 != null && tmpField1 != null && tmpField0 != tmpField1) {
// construct a new field
final Field fLeft = tmpField0;
final Field fRight = tmpField1;
// we'll get an Assocation.Event callback from the add, so make sure these are null first
tmpField0 = tmpField1 = null;
Association.add(fLeft, fRight);
} else {
fireTableRowsUpdated(row, row);
}
}
public void deleteAssociation(int index) {
Association.remove(Association.get(index));
// we'll get an Assocation.Event callback for the table update
}
public void toggleAssociation(int index) {
final Association a = Association.get(index);
if (a != null) {
a.setEnabled(!a.isEnabled());
Log.debug("toggled " + a);
}
fireTableRowsUpdated(index, index);
}
}
//===================================================================================================
// protected class AssociationsTableModel extends AbstractTableModel {
// static final long serialVersionUID = 1;
// protected Vector<Vector<Object>> associations = new Vector<Vector<Object>>();
// public int getRowCount() {
// return associations.size();
// }
// public int getColumnCount() {
// return 4;
// }
// public boolean isCellEditable(int row, int column) {
// return (column == 0);
// }
// public Class getColumnClass(int column) {
// return (associations.size() > 0 ? getValueAt(0, column).getClass() : null);
// }
// public Object getValueAt(int row, int column) {
// Object result = null;
// switch (column){
// case 0: // column 0 contains the Boolean stored in the Vector's 0th element (displays as a checkbox)
// case 1: // column 1 contains the String stored in the Vector's 1st element
// result = associations.elementAt(row).elementAt(column);
// break;
// case 2: // column 2 contains an equals sign
// result = "=";
// break;
// case 3: // column 3 contains the String stored in the Vector's 2nd element
// result = associations.elementAt(row).elementAt(2);
// break;
// }
// if (result == null) {
// result = VueResources.getString("associationsPane.chooseField");
// }
// return result;
// }
// public void setValueAt(Object obj, int row, int column) {
// switch (column){
// case 0: // column 0 contains the Boolean stored in the Vector's 0th element (displays as a checkbox)
// case 2: // column 2 contains an equals sign
// break;
// case 1: // column 1 contains the String stored in the Vector's 1st element
// case 3: // column 3 contains the String stored in the Vector's 2nd element
// Vector<Object> association = associations.elementAt(row);
// association.setElementAt(obj, column == 3 ? 2 : column);
// if (association.elementAt(1) != null && association.elementAt(2) != null) {
// association.setElementAt(new Boolean(true), 0);
// }
// fireTableRowsUpdated(row, row);
// break;
// }
// }
// public void addAssociation(int index) {
// Vector<Object> association = new Vector<Object>();
// association.add(new Boolean(false));
// association.add(null);
// association.add(null);
// associations.add(index, association);
// fireTableRowsInserted(index, index);
// }
// public void deleteAssociation(int index) {
// associations.remove(index);
// fireTableRowsDeleted(index, index);
// }
// public void toggleAssociation(int index) {
// Vector<Object> association = associations.elementAt(index);
// if (association.elementAt(1) != null && association.elementAt(2) != null) {
// boolean currentState = ((Boolean)(association.elementAt(0))).booleanValue();
// association.setElementAt(new Boolean(!currentState), 0);
// }
// fireTableRowsUpdated(index, index);
// }
// }
protected class BlankRenderer extends DefaultTableCellRenderer {
static final long serialVersionUID = 1;
JLabel emptyLabel = new JLabel("");
public Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus,
int row, int column) {
return emptyLabel;
}
}
protected class GrayRenderer extends DefaultTableCellRenderer {
static final long serialVersionUID = 1;
JLabel grayLabel = new JLabel("");
{
grayLabel.setFont(GUI.LabelFace);
}
public Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus, int row, int column) {
String stringValue = value.toString();
boolean isChooseField = stringValue.equals(CHOOSE_FIELD);
grayLabel.setText(stringValue);
grayLabel.setForeground(isChooseField ? Color.GRAY : Color.BLACK);
grayLabel.setToolTipText(isChooseField ? stringValue : null);
return grayLabel;
}
}
protected class AssociationsDropTarget extends DropTarget {
static final long serialVersionUID = 1;
public void dragEnter(DropTargetDragEvent event) {
Point dropLocation = event.getLocation();
int row = associationsTable.rowAtPoint(dropLocation);
if (row == Association.getCount()) {
event.acceptDrag(DnDConstants.ACTION_COPY);
}
}
public void dragExit(DropTargetEvent event) {}
public void dragOver(DropTargetDragEvent event) {
Point dropLocation = event.getLocation();
int row = associationsTable.rowAtPoint(dropLocation);
if (row == Association.getCount()) {
event.acceptDrag(DnDConstants.ACTION_COPY);
}
}
public void drop(DropTargetDropEvent event) {
Point dropLocation = event.getLocation();
int row = associationsTable.rowAtPoint(dropLocation),
column = associationsTable.columnAtPoint(dropLocation);
if (row == Association.getCount()) {
event.acceptDrop(DnDConstants.ACTION_COPY);
event.dropComplete(dropAssociation(event.getTransferable(), row, column));
}
}
public void dropActionChanged(DropTargetDragEvent event) {
event.acceptDrag(DnDConstants.ACTION_COPY);
}
}
protected class AssociationsListSelectionListener implements ListSelectionListener {
public void valueChanged(ListSelectionEvent event) {
enableMenuActions();
}
}
protected class AssociationsMouseListener extends MouseAdapter {
public void mouseReleased(MouseEvent event) {
toggleAssociation();
}
}
// protected class AddAssociationListener extends MouseAdapter {
// public void mouseClicked(MouseEvent event) {
// addAssociationAction.actionPerformed(null);
// }
// }
}