/*
* 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.
*/
/*
* AbstractListValueEditor.java
* Created: Feb 23, 2004
* By: Richard Webster
*/
package org.openquark.gems.client.valueentry;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.GridLayout;
import java.awt.Insets;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Collections;
import java.util.List;
import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTable;
import javax.swing.JViewport;
import javax.swing.ScrollPaneConstants;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.border.BevelBorder;
import javax.swing.border.EmptyBorder;
import javax.swing.border.EtchedBorder;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableModel;
import org.openquark.cal.compiler.TypeExpr;
import org.openquark.cal.valuenode.ValueNode;
import org.openquark.gems.client.ToolTipHelpers;
/**
* This is a super class for value editors which display a list-like interface to the user.
* This class should not assume that it is editing a specific type of value node (such as a ListValueNode),
* but instead work with a AbstractListTableModel to manipulate the value.
* @author Richard Webster
*/
public abstract class AbstractListValueEditor extends TableValueEditor {
/**
* KeyListener used to listen for user's commit (Enter), cancel (Esc) input.
* Register the key listener to the buttons of this ListTupleValueEditor.
*/
class ListTupleValueEditorKeyListener extends KeyAdapter {
@Override
public void keyPressed(KeyEvent evt) {
if (evt.getKeyCode() == KeyEvent.VK_ENTER) {
handleCommitGesture();
evt.consume(); // Don't want the button to activate its action event.
} else if (evt.getKeyCode() == KeyEvent.VK_ESCAPE) {
handleCancelGesture();
evt.consume(); // Don't want the button to activate its action event.
}
}
}
private class IvjEventHandler implements ActionListener {
public void actionPerformed(ActionEvent e) {
if (e.getSource() == AbstractListValueEditor.this.getAddButton()) {
addButton_ActionEvents();
} else if (e.getSource() == AbstractListValueEditor.this.getRemoveButton()) {
removeButton_ActionEvents();
} else if (e.getSource() == AbstractListValueEditor.this.getUpButton()) {
upButton_ActionEvents();
} else if (e.getSource() == AbstractListValueEditor.this.getDownButton()) {
downButton_ActionEvents();
}
}
}
/**
* Model for an uneditable table. Calls to isCellEditable() always return false.
* @author Iulian Radu
*/
private static class NoneditableTableModel extends DefaultTableModel {
private static final long serialVersionUID = 821263469183246794L;
@Override
public boolean isCellEditable(int row, int col) {
return false;
}
}
/**
* A simple one-column table whose row cells hold as values the row indices from another table.
* The cells are rendered to look as header cells of the other table, and their width corresponds
* to the other table row width, such that this object can be used as a left-hand-side table header.
*
* This header attaches listeners to the table model, watching for insertions/deletions and updating
* itself as necessary; also, a listener is attached to the table itself to watch for model replacement
* events, in which case this object switches to listen for changes on the new model.
*
* @author Iulian Radu
**/
static class ListRowHeader extends JTable {
private static final long serialVersionUID = 2251011377716682243L;
/**
* Renderer which renders cells in a similar style to the table's column header.
* @author Iulian Radu
*/
private class CellRenderer extends DefaultTableCellRenderer {
private static final long serialVersionUID = -5481784220610404701L;
CellRenderer () {
}
/**
* {@inheritDoc}
*/
@Override
public Component getTableCellRendererComponent(JTable table,
Object value, boolean isSelected, boolean hasFocus, int row,
int column) {
JTableHeader tableHeader = indexedTable.getTableHeader();
if (tableHeader != null) {
setForeground(tableHeader.getForeground());
setBackground(tableHeader.getBackground());
setFont(tableHeader.getFont());
setBorder(UIManager.getBorder("TableHeader.cellBorder"));
} else {
setForeground(UIManager.getColor("TableHeader.foreground"));
setBackground(UIManager.getColor("TableHeader.background"));
setFont(UIManager.getFont("TableHeader.font"));
setBorder(UIManager.getBorder("TableHeader.cellBorder"));
}
setText((value == null) ? "" : value.toString());
return this;
}
}
/**
* Listener for the original table model. This updates the row index header whenever
* there are rows added/deleted from the table.
* @author Iulian Radu
*/
private class ModelChangeListener implements TableModelListener {
/**
* Update the header in response to an update of the table model.
* @see javax.swing.event.TableModelListener#tableChanged(javax.swing.event.TableModelEvent)
*/
public void tableChanged(TableModelEvent e) {
if (e.getType() == TableModelEvent.DELETE || e.getType() == TableModelEvent.INSERT) {
initialize(indexedTable.getModel());
}
}
}
/** Table which this header is indexing */
private final JTable indexedTable;
/** Change listener for the table model */
private final ModelChangeListener modelChangeListener;
/**
* Constructor
* @param table which this row header attaches to
*/
public ListRowHeader(JTable table) {
super(new NoneditableTableModel());
((DefaultTableModel)this.getModel()).addColumn("rowCountColumn");
this.getColumn("rowCountColumn").setCellRenderer(new CellRenderer());
this.indexedTable = table;
this.setRowHeight(table.getRowHeight());
this.modelChangeListener = new ModelChangeListener();
// In case the table model changes, switch the listener to the new model
table.addPropertyChangeListener("model", new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent evt) {
((TableModel)evt.getOldValue()).removeTableModelListener(modelChangeListener);
((TableModel)evt.getNewValue()).addTableModelListener(modelChangeListener);
initialize(((TableModel)evt.getNewValue()));
}
});
}
/**
* Initialize table with proper field names and listeners.
* @param tableModel model to use
*/
private void initialize(TableModel tableModel) {
DefaultTableModel model = ((DefaultTableModel)this.getModel());
for (int i = 0, n = model.getRowCount(); i<n; i++) {
model.removeRow(0);
}
// Get names of all fields and put them in the table
for (int i = 0, n = tableModel.getRowCount(); i < n; i++) {
List<String> rowElement = Collections.singletonList(Integer.toString(i));
model.addRow(rowElement.toArray());
}
}
}
/** The miniums size of this editor. */
private static final Dimension MIN_SIZE = new Dimension(200, 200);
/** The maximum size of this editor. */
private static final Dimension MAX_SIZE = new Dimension(600, 383);
/** The minimum size if the editor is not editable. */
private static final Dimension NOT_EDITABLE_MIN_SIZE = new Dimension(MIN_SIZE.width, 80);
// Fields to hold on to all the UI components for this value editor
private JButton ivjAddButton = null;
private JPanel ivjArrowPanel = null;
private JPanel ivjButtonPanel1 = null;
private JPanel ivjButtonPanel2 = null;
private JPanel ivjButtonPanel3 = null;
private JButton ivjDownButton = null;
private JPanel ivjEastPanel = null;
private JLabel ivjElementIcon = null;
private JButton ivjRemoveButton = null;
private JPanel ivjSouthPanel = null;
private JButton ivjUpButton = null;
private final IvjEventHandler ivjEventHandler = new IvjEventHandler();
private JPanel ivjCenterPanel = null;
private JPanel ivjDivideLeftPanel = null;
private JPanel ivjDivideRightPanel = null;
private ListRowHeader elementIndexHeader = null;
private JScrollPane divideLeftScrollPane = null;
private JScrollPane divideRightScrollPane = null;
private JSplitPane centerSplitPanel;
private int dividerLocation = -1;
/**
* Constructor for AbstractListValueEditor.
* @param valueEditorHierarchyManager the hierarchy manager for the editor
*/
protected AbstractListValueEditor(ValueEditorHierarchyManager valueEditorHierarchyManager) {
super(valueEditorHierarchyManager);
initialize();
}
/**
* Initialize the class.
* Note: Extra set-up code has been added.
*/
private void initialize() {
try {
setName("ListTupleValueEditor");
setFocusCycleRoot(true);
resetComponents();
initConnections();
} catch (Throwable ivjExc) {
handleException(ivjExc);
}
// Register the special key listener to all buttons.
ListTupleValueEditorKeyListener ltveKeyListener = new ListTupleValueEditorKeyListener();
getUpButton().addKeyListener(ltveKeyListener);
getDownButton().addKeyListener(ltveKeyListener);
getAddButton().addKeyListener(ltveKeyListener);
getRemoveButton().addKeyListener(ltveKeyListener);
// Set default cursor for components that should have only ever have default cursors.
getAddButton().setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
getRemoveButton().setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
getUpButton().setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
getDownButton().setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
}
/**
* @see org.openquark.gems.client.valueentry.TableValueEditor#createTableModel(org.openquark.cal.valuenode.ValueNode)
*/
@Override
protected ValueEditorTableModel createTableModel(ValueNode valueNode) {
return createListTableModel(valueNode);
}
/**
* Returns a new model to be used for displaying and editing the data as a list.
* Subclasses should implement this instead of createTableModel.
* @return a new model to be used for displaying and editing the data as a list
*/
protected abstract AbstractListTableModel createListTableModel(ValueNode valueNode);
/**
* Returns an interface for manipulating the contents of the list.
* @return an interface for manipulating the contents of the list
*/
protected AbstractListTableModel getListTableModel() {
return (AbstractListTableModel) tableModel;
}
/**
* Adds all of the subcomponents to 'this'. The subcomponents include the centre panel showing the
* table, the east components showing up/down arrows, and the south component showing the add/remove
* item buttons. If the value editor is editable (determined by calling isEditable()) then all
* components are shown. If the editor is not editable then only the centre component is shown for
* viewing the data.
*/
protected void resetComponents() {
// Ensure that the old components are removed.
removeAll();
setLayout(new BorderLayout());
if (isEditable()) {
// Add all the components
add(getSouthPanel(), "South");
add(getEastPanel(), "East");
add(getCenterPanel(), "Center");
} else {
// Only add the viewing components
add(getCenterPanel(), "Center");
if (getRowCount() == 0) {
JLabel message = new JLabel(ValueEditorMessages.getString("VE_EmptyList"));
message.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
message.setHorizontalAlignment(SwingConstants.CENTER);
add(message, "South");
}
}
// Validate the new layout
validate();
}
/**
* @see org.openquark.gems.client.valueentry.ValueEditor#setEditable(boolean)
*/
@Override
public void setEditable(boolean b) {
super.setEditable(b);
// Reset the visible components (this may hide or show components that are only necessary when the
// value editor is in an editable state)
resetComponents();
}
/**
* Moves the selected row up one.
*/
protected void upButton_ActionEvents() {
int selectedRow = getSelectedRow();
int selectedColumn = getSelectedColumn();
clearSelection(true);
getListTableModel().moveRowUp(selectedRow);
selectCell(selectedRow - 1, selectedColumn);
}
/**
* Move the selected row one down.
*/
protected void downButton_ActionEvents() {
int selectedRow = getSelectedRow();
int selectedColumn = getSelectedColumn();
clearSelection(true);
getListTableModel().moveRowDown(selectedRow);
selectCell(selectedRow + 1, selectedColumn);
}
/**
* Adds a new row with default values to the table.
*/
protected void addButton_ActionEvents() {
clearSelection(true);
getListTableModel().addRow();
// Highlight the newly added row.
selectCell(getRowCount() - 1, getSelectedColumn());
updateIconToolTip();
updateRowHeaderDivider();
// Ensure that the screen is updated to reflect the changes. I don't think this should be necessary,
// but it does fix the problem of not updating correctly and nothing else seems to...
validate();
repaint();
}
/**
* Removes the selected row.
*/
protected void removeButton_ActionEvents() {
int oldSelectedRow = getSelectedRow();
clearSelection(false);
getListTableModel().removeRow(oldSelectedRow);
// Handle selection, and enabling/disabling remove button.
int rowCount = getRowCount();
if (rowCount != 0) {
int newSelectedRow = 0;
// Update the highlighted/selected row and edited cell.
if (rowCount == oldSelectedRow) {
// Move highlight/selection up one row, since we just deleted the last row.
newSelectedRow = oldSelectedRow - 1;
} else {
// Keep the selection in its old row.
newSelectedRow = oldSelectedRow;
}
selectCell(newSelectedRow, getSelectedColumn());
}
enableButtonsForTableState();
updateIconToolTip();
updateRowHeaderDivider();
// Ensure that the screen is updated to reflect the changes. I don't think this should be necessary,
// but it does fix the problem of not updating correctly and nothing else seems to...
validate();
repaint();
}
/**
* Return the AddButton property value.
* @return JButton
*/
protected JButton getAddButton() {
if (ivjAddButton == null) {
try {
ivjAddButton = new JButton();
ivjAddButton.setName("AddButton");
ivjAddButton.setMnemonic('a');
ivjAddButton.setText(ValueEditorMessages.getString("VE_AddButtonLabel"));
} catch (Throwable ivjExc) {
handleException(ivjExc);
}
}
return ivjAddButton;
}
/**
* Return the ArrowPanel property value.
* @return JPanel
*/
private JPanel getArrowPanel() {
if (ivjArrowPanel == null) {
try {
ivjArrowPanel = new JPanel();
ivjArrowPanel.setName("ArrowPanel");
ivjArrowPanel.setLayout(getArrowPanelGridLayout());
getArrowPanel().add(getUpButton(), getUpButton().getName());
getArrowPanel().add(getDownButton(), getDownButton().getName());
} catch (Throwable ivjExc) {
handleException(ivjExc);
}
}
return ivjArrowPanel;
}
/**
* Return the ArrowPanelGridLayout property value.
* @return GridLayout
*/
private GridLayout getArrowPanelGridLayout() {
GridLayout ivjArrowPanelGridLayout = null;
try {
/* Create part */
ivjArrowPanelGridLayout = new GridLayout();
ivjArrowPanelGridLayout.setRows(2);
ivjArrowPanelGridLayout.setVgap(5);
} catch (Throwable ivjExc) {
handleException(ivjExc);
}
return ivjArrowPanelGridLayout;
}
/**
* Return the ButtonPanel1 property value.
* @return JPanel
*/
private JPanel getButtonPanel1() {
if (ivjButtonPanel1 == null) {
try {
ivjButtonPanel1 = new JPanel();
ivjButtonPanel1.setName("ButtonPanel1");
ivjButtonPanel1.setLayout(new FlowLayout());
getButtonPanel1().add(getButtonPanel2(), getButtonPanel2().getName());
} catch (Throwable ivjExc) {
handleException(ivjExc);
}
}
return ivjButtonPanel1;
}
/**
* Return the ButtonPanel2 property value.
* @return JPanel
*/
private JPanel getButtonPanel2() {
if (ivjButtonPanel2 == null) {
try {
ivjButtonPanel2 = new JPanel();
ivjButtonPanel2.setName("ButtonPanel2");
ivjButtonPanel2.setLayout(getButtonPanel2FlowLayout());
getButtonPanel2().add(getButtonPanel3(), getButtonPanel3().getName());
} catch (Throwable ivjExc) {
handleException(ivjExc);
}
}
return ivjButtonPanel2;
}
/**
* Return the ButtonPanel2FlowLayout property value.
* @return FlowLayout
*/
private FlowLayout getButtonPanel2FlowLayout() {
FlowLayout ivjButtonPanel2FlowLayout = null;
try {
/* Create part */
ivjButtonPanel2FlowLayout = new FlowLayout();
ivjButtonPanel2FlowLayout.setAlignment(FlowLayout.RIGHT);
ivjButtonPanel2FlowLayout.setVgap(0);
ivjButtonPanel2FlowLayout.setHgap(0);
} catch (Throwable ivjExc) {
handleException(ivjExc);
}
return ivjButtonPanel2FlowLayout;
}
/**
* Return the ButtonPanel3 property value.
* @return JPanel
*/
private JPanel getButtonPanel3() {
if (ivjButtonPanel3 == null) {
try {
ivjButtonPanel3 = new JPanel();
ivjButtonPanel3.setName("ButtonPanel3");
ivjButtonPanel3.setLayout(getButtonPanel3GridLayout());
getButtonPanel3().add(getAddButton(), getAddButton().getName());
getButtonPanel3().add(getRemoveButton(), getRemoveButton().getName());
} catch (Throwable ivjExc) {
handleException(ivjExc);
}
}
return ivjButtonPanel3;
}
/**
* Return the ButtonPanel3GridLayout property value.
* @return GridLayout
*/
private GridLayout getButtonPanel3GridLayout() {
GridLayout ivjButtonPanel3GridLayout = null;
try {
/* Create part */
ivjButtonPanel3GridLayout = new GridLayout();
ivjButtonPanel3GridLayout.setHgap(5);
} catch (Throwable ivjExc) {
handleException(ivjExc);
}
return ivjButtonPanel3GridLayout;
}
/**
* Return the CenterPanel property value.
* @return JPanel
*/
protected JPanel getCenterPanel() {
if (ivjCenterPanel == null) {
ivjCenterPanel = createCenterPanel();
}
return ivjCenterPanel;
}
/**
* @return split panel used in the center of the VEP
*/
protected JSplitPane getCenterSplitPanel() {
if (centerSplitPanel == null) {
centerSplitPanel = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
centerSplitPanel.setName("SplitCenterPanel");
centerSplitPanel.setDividerSize(2);
centerSplitPanel.setDividerLocation(50);
centerSplitPanel.setLeftComponent(getDivideLeftPanel());
centerSplitPanel.setRightComponent(getDivideRightPanel());
centerSplitPanel.setBorder(BorderFactory.createLoweredBevelBorder());
}
return centerSplitPanel;
}
/**
* @return panel used in the center of the VEP
*/
protected JPanel createCenterPanel() {
JPanel centerPanel = new JPanel(new BorderLayout());
centerPanel.add(getCenterSplitPanel(), BorderLayout.CENTER);
return centerPanel;
}
/**
* @return the panel used on the right hand side of the divider pane
*/
protected JPanel getDivideRightPanel() {
if (ivjDivideRightPanel == null) {
ivjDivideRightPanel = createDivideRightPanel();
}
return ivjDivideRightPanel;
}
/**
* @return new panel to use on the right hand side of the divider pane
*/
protected JPanel createDivideRightPanel() {
JPanel ivjDivideRightPanel = new JPanel();
ivjDivideRightPanel.setName("DivideRightPanel");
ivjDivideRightPanel.setLayout(new BorderLayout());
ivjDivideRightPanel.add(getDivideRightScrollPane(), "Center");
return ivjDivideRightPanel;
}
/**
* @return the panel used on the left hand side of the divider pane
*/
protected JPanel getDivideLeftPanel() {
if (ivjDivideLeftPanel == null) {
ivjDivideLeftPanel = createDivideLeftPanel();
}
return ivjDivideLeftPanel;
}
/**
* Creates the panel on the left side of the divider pane.
* This panel contains the column header " # ", and the left divide scroll pane.
*
* @return new panel to use on the left side of the divide pane
*/
protected JPanel createDivideLeftPanel() {
// Create panel for whole left side (this includes the row header and another padding label on top)
JPanel leftDivisionPanel = new JPanel(new BorderLayout());
JLabel ln = new JLabel(" # ");
ln.setBorder(new BevelBorder(BevelBorder.RAISED, Color.GRAY, Color.WHITE, Color.GRAY, Color.LIGHT_GRAY));
ln.setPreferredSize(new Dimension(1, table.getRowHeight() - 5));
leftDivisionPanel.add(ln, BorderLayout.NORTH);
leftDivisionPanel.add(getDivideLeftScrollPane(), BorderLayout.CENTER);
leftDivisionPanel.setBorder(BorderFactory.createEmptyBorder());
leftDivisionPanel.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
return leftDivisionPanel;
}
/**
* Get the scroll panel used for the left side of the divide pane.
* This scroll panel does not contain the " # " column header (since this should not scroll).
*
* The scroll panel contains the following components:
* - a panel containing
* - the table row header (ie: a table whose cells indicate row indices)
* - a padding underneath the table (this becomes visible when the panel is stretched vertically
* past the table height)
* - a padding underneath the panel (this becomes visible only when there is a horizontal scrollbar
* on the right side of the divider)
*
* @return the left divider scroll pane (this contains the list row header by default)
*/
private JScrollPane getDivideLeftScrollPane() {
if (divideLeftScrollPane == null) {
JPanel ivjDivideLeftPanel = new JPanel();
ivjDivideLeftPanel.setName("DivideLeftPanel");
ivjDivideLeftPanel.setLayout(new BorderLayout());
// Create padding on the north and south side
// (if there is a horizontal scrollbar on the right side, this label will be expanded)
JLabel ls = new JLabel(" ");
ls.setPreferredSize(new Dimension(0,0));
ivjDivideLeftPanel.add(ls, BorderLayout.SOUTH);
ivjDivideLeftPanel.add(getElementIndexHeader(), BorderLayout.CENTER);
ivjDivideLeftPanel.setBorder(BorderFactory.createEmptyBorder());
// Create padding on the center side
// (this will show if the window is expanded vertically past the lineRowHeader height)
JPanel rowHeaderPaddedPanel = new JPanel(new BorderLayout());
JLabel lc = new JLabel(" ");
// Note: If we set preferred size of this label to 0, it will not be rendered when it
// is supposed to, manifesting a transparency bug.
rowHeaderPaddedPanel.add(ivjDivideLeftPanel, BorderLayout.NORTH);
rowHeaderPaddedPanel.setBorder(BorderFactory.createEmptyBorder());
// Encase everything in a scroll pane so that we can scroll
final JScrollPane rowHeaderScrollPane = new JScrollPane(rowHeaderPaddedPanel);
rowHeaderPaddedPanel.add(lc, BorderLayout.CENTER);
rowHeaderScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
rowHeaderScrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER);
rowHeaderScrollPane.setBorder(BorderFactory.createEmptyBorder());
divideLeftScrollPane = rowHeaderScrollPane;
divideLeftScrollPane.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
}
return divideLeftScrollPane;
}
/**
* @return the right scroll pane (this is the tableScrollPane by default)
*/
private JScrollPane getDivideRightScrollPane() {
if (divideRightScrollPane == null) {
divideRightScrollPane = tableScrollPane;
// Create small panel for upper right corner of main scroll pane
JPanel cornerBorderPanel = new JPanel();
cornerBorderPanel.setBorder(new BevelBorder(BevelBorder.RAISED, Color.GRAY, Color.WHITE, Color.GRAY, Color.LIGHT_GRAY));
tableScrollPane.setCorner(ScrollPaneConstants.UPPER_RIGHT_CORNER, cornerBorderPanel);
tableScrollPane.setBorder(BorderFactory.createEmptyBorder());
}
return divideRightScrollPane;
}
/**
* @return the list row header
*/
protected JTable getElementIndexHeader() {
if (elementIndexHeader == null) {
elementIndexHeader = new ListRowHeader(table);
// The index 'header' is actually a table; it shouldn't have a column header of its own
elementIndexHeader.setTableHeader(null);
}
return elementIndexHeader;
}
/**
* Returns whether an the type icon should be displayed in the editor.
*/
protected boolean displayElementIcon () {
// By default, display the element icon.
return true;
}
/**
* Return the DownButton property value.
* @return JButton
*/
protected JButton getDownButton() {
if (ivjDownButton == null) {
try {
ivjDownButton = new JButton();
ivjDownButton.setName("DownButton");
ivjDownButton.setToolTipText(ValueEditorMessages.getString("VE_MoveElementDownToolTip"));
ivjDownButton.setMnemonic('d');
ivjDownButton.setText("");
ivjDownButton.setIcon(new ImageIcon(AbstractListValueEditor.class.getResource("/Resources/down.gif")));
ivjDownButton.setMargin(new Insets(0, 0, 0, 0));
} catch (Throwable ivjExc) {
handleException(ivjExc);
}
}
return ivjDownButton;
}
/**
* Return the EastPanel property value.
* @return JPanel
*/
protected JPanel getEastPanel() {
if (ivjEastPanel == null) {
ivjEastPanel = createEastPanel();
}
return ivjEastPanel;
}
/**
* @return new EastPanel
*/
protected JPanel createEastPanel() {
JPanel ivjEastPanel = new JPanel();
ivjEastPanel.setName("EastPanel");
ivjEastPanel.setBorder(new EtchedBorder());
ivjEastPanel.setLayout(new FlowLayout());
ivjEastPanel.add(getArrowPanel(), getArrowPanel().getName());
return ivjEastPanel;
}
/**
* Return the ElementIcon property value.
* Note: Extra set-up code has been added.
* @return JLabel
*/
private JLabel getElementIcon() {
if (ivjElementIcon == null) {
try {
ivjElementIcon = new JLabel();
ivjElementIcon.setName("ElementIcon");
ivjElementIcon.setIcon(new ImageIcon(getClass().getResource("/Resources/notype.gif")));
ivjElementIcon.setText("");
ivjElementIcon.setBorder(new EmptyBorder(0, 3, 0, 0));
} catch (Throwable ivjExc) {
handleException(ivjExc);
}
}
return ivjElementIcon;
}
/**
* Return the RemoveButton property value.
* @return JButton
*/
protected JButton getRemoveButton() {
if (ivjRemoveButton == null) {
try {
ivjRemoveButton = new JButton();
ivjRemoveButton.setName("RemoveButton");
ivjRemoveButton.setMnemonic('r');
ivjRemoveButton.setText(ValueEditorMessages.getString("VE_RemoveButtonLabel"));
} catch (Throwable ivjExc) {
handleException(ivjExc);
}
}
return ivjRemoveButton;
}
/**
* Return the SouthPanel property value.
* @return JPanel
*/
protected JPanel getSouthPanel() {
if (ivjSouthPanel == null) {
ivjSouthPanel = createSouthPanel();
}
return ivjSouthPanel;
}
/** @return newly created SouthPanel */
protected JPanel createSouthPanel() {
JPanel ivjSouthPanel = new JPanel();
ivjSouthPanel.setName("SouthPanel");
ivjSouthPanel.setLayout(new BorderLayout());
if (displayElementIcon()) {
ivjSouthPanel.add(getElementIcon(), "West");
}
ivjSouthPanel.add(getButtonPanel1(), "East");
return ivjSouthPanel;
}
/**
* Return the UpButton property value.
* @return JButton
*/
protected JButton getUpButton() {
if (ivjUpButton == null) {
try {
ivjUpButton = new JButton();
ivjUpButton.setName("UpButton");
ivjUpButton.setToolTipText(ValueEditorMessages.getString("VE_MoveElementUpToolTip"));
ivjUpButton.setMnemonic('u');
ivjUpButton.setText("");
ivjUpButton.setIcon(new ImageIcon(getClass().getResource("/Resources/up.gif")));
ivjUpButton.setMargin(new Insets(0, 0, 0, 0));
} catch (Throwable ivjExc) {
handleException(ivjExc);
}
}
return ivjUpButton;
}
/**
* {@inheritDoc}
*/
@Override
public void handleCellActivated() {
super.handleCellActivated();
enableButtonsForTableState();
}
/**
* {@inheritDoc}
*/
@Override
public void handleElementLaunchingEditor() {
super.handleElementLaunchingEditor();
getAddButton().setEnabled(false);
getRemoveButton().setEnabled(false);
getUpButton().setEnabled(false);
getDownButton().setEnabled(false);
}
/**
* Enable/Disable the buttons according to the current state of the table.
* Creation date: (03/14/02 11:21:00 AM)
*/
protected void enableButtonsForTableState() {
// Find out current editable state.
boolean isEditable = isEditable();
int selectedRow = getSelectedRow();
if (selectedRow == -1 || getRowCount() == 0) {
getAddButton().setEnabled(isEditable());
getRemoveButton().setEnabled(false);
getUpButton().setEnabled(false);
getDownButton().setEnabled(false);
} else {
// Enable/disable add and remove.
getAddButton().setEnabled(isEditable);
getRemoveButton().setEnabled(isEditable);
boolean isFirstRow = (selectedRow == 0);
boolean isLastRow = (selectedRow == getRowCount() - 1);
// Enable/disable up/down buttons.
getUpButton().setEnabled(isEditable && !isFirstRow);
getDownButton().setEnabled(isEditable && !isLastRow);
}
}
/**
* Called whenever the part throws an exception.
* @param exception Throwable
*/
private void handleException(Throwable exception) {
/* Uncomment the following lines to print uncaught exceptions to stdout */
System.out.println("--------- UNCAUGHT EXCEPTION ---------");
exception.printStackTrace(System.out);
}
/**
* Initializes connections
* @exception Exception The exception description.
*/
private void initConnections() throws Exception {
getAddButton().addActionListener(ivjEventHandler);
getRemoveButton().addActionListener(ivjEventHandler);
getUpButton().addActionListener(ivjEventHandler);
getDownButton().addActionListener(ivjEventHandler);
// Scroll both table and row header when one scrolls
getDivideLeftScrollPane().getViewport().addChangeListener(new ChangeListener() {
public void stateChanged(ChangeEvent e) {
getDivideRightScrollPane().getViewport().setViewPosition(new Point(getDivideRightScrollPane().getViewport().getViewPosition().x, ((JViewport)e.getSource()).getViewPosition().y));
}
});
getDivideRightScrollPane().getViewport().addChangeListener(new ChangeListener() {
public void stateChanged(ChangeEvent e) {
getDivideLeftScrollPane().getViewport().setViewPosition(new Point(getDivideLeftScrollPane().getViewport().getViewPosition().x, ((JViewport)e.getSource()).getViewPosition().y));
}
});
}
/**
* Update the location of the split panel divider to show the whole row index text,
* if it is not already doing so.
*
* Ex: When creating a new list, this method shifts the divider right when expanding
* the list from 9 to 10 rows.
*/
protected void updateRowHeaderDivider() {
if (elementIndexHeader == null || getCenterSplitPanel() == null) {
return;
}
int width = SwingUtilities.computeStringWidth(elementIndexHeader.getFontMetrics(elementIndexHeader.getFont()), "" + (table.getRowCount()-1));
width = Math.max(18, width + 10);
if (dividerLocation < width) {
dividerLocation = width;
getCenterSplitPanel().setDividerLocation(dividerLocation);
}
}
/**
* @see org.openquark.gems.client.valueentry.ValueEditor#setInitialValue()
*/
@Override
public void setInitialValue() {
// If we're not editable remove the minimum height constraints.
// That way the editor will size itself to the number of rows actually in it.
// For example, the min size of 5 rows will not be used if there are only 2 rows
// in the list. We still use 20 as a minimum in case the list is empty.
Dimension minSize = isEditable() ? MIN_SIZE : NOT_EDITABLE_MIN_SIZE;
initSize(minSize, MAX_SIZE);
// Enlarge list row header divider to show full text of row indices, if not already doing so
updateRowHeaderDivider();
// Do some additional size tweaking below.
// Note: We only constrain the height of the editor, not the width.
// It should also be possible for the user to make the list wider since
// the content in the value entry panels may not fit.
if (isEditable()) {
// We only care about our maximum size for the initial display.
// Afterwards we allow the user to resize the editor however he wants.
setMaxResizeDimension(null);
} else if (getRowCount() == 0) {
// No need to make the list any bigger, there's nothing in it.
setMaxResizeDimension(NOT_EDITABLE_MIN_SIZE);
} else {
// There's some content in the list, but it's not editable.
Dimension size = getSize();
Dimension prefSize = getPreferredSize();
// Note: If the center panel contains only a table with displayed column header,
// preferred size does not include the table header if getParent() == null. This is a
// bug, and can be resolved by adding the table header height to the prefSize.height.
// Set the maximum size to the preferred size. The user never needs
// to make the editor any bigger since it's not editable.
setMaxResizeDimension(new Dimension(2048, prefSize.height));
// If the list is small then size the editor to fit it exactly.
if (prefSize.height <= MIN_SIZE.height) {
setMinResizeDimension(new Dimension(MIN_SIZE.width, prefSize.height));
setSize(new Dimension(size.width, prefSize.height));
}
}
validate();
userHasResized();
}
/**
* @see org.openquark.gems.client.valueentry.ValueEditor#refreshDisplay()
*/
@Override
public void refreshDisplay() {
// Reselect the cell that was previously being edited.
selectCell(getSelectedRow(), getSelectedColumn());
// Update the displayed icon and its tooltip.
updateIconToolTip();
String iconName = valueEditorManager.getTypeIconName(getValueNode().getTypeExpr());
getElementIcon().setIcon(new ImageIcon(ListTupleValueEditor.class.getResource(iconName)));
enableButtonsForTableState();
}
/**
* {@inheritDoc}
*/
@Override
protected void userHasResized() {
updateColumnSizes();
}
/**
* Updates the sizes of the table columns via the auto-resize function of the table,
* such that either the columns are all proportionately stretched to fit screen size,
* or the auto-resize is turned off to allow columns to display their preferred size.
*/
protected void updateColumnSizes() {
Dimension tableScrollPaneSize = tableScrollPane.getSize();
if (tableScrollPaneSize.width == 0) {
// The sizes of editor components have not been initialized.
// Invoke this method later, because it depends on the size of the table scroll pane.
SwingUtilities.invokeLater(new Runnable() {
public void run() {
updateColumnSizes();
}
});
return;
}
// Calculate the combined width of the columns (this is the minimum size of the scroll pane
// when no automatic stretching occurs).
int tableActualWidth = 0;
for (int i = 0, colCount = table.getColumnCount(); i < colCount; i++) {
tableActualWidth += table.getColumn(table.getColumnName(i)).getPreferredWidth();
}
// Now if the current view of the table is larger than the table itself, automatically resize it
int newResizeMode;
if (tableScrollPaneSize.width >= tableActualWidth) {
newResizeMode = JTable.AUTO_RESIZE_SUBSEQUENT_COLUMNS;
} else {
newResizeMode = JTable.AUTO_RESIZE_OFF;
}
table.setAutoResizeMode(newResizeMode);
validate();
}
/**
* Call this whenever the icon's tooltip needs to be refreshed.
* What happens is that the number of elements in this list will be shown
* in the tooltip.
* Creation date: (16/03/01 2:30:09 PM)
*/
protected void updateIconToolTip() {
String iconToolTip = valueEditorManager.getTypeName(getValueNode().getTypeExpr());
int elementCount = getListTableModel().getRowCount();
iconToolTip += ValueEditorMessages.getString("VE_OfLengthToolTip", new Integer(elementCount));
iconToolTip = "<html><body>" + ToolTipHelpers.wrapTextToHTMLLines(iconToolTip, this) + "</body></html>";
getElementIcon().setToolTipText(iconToolTip);
}
/**
* @return type expression of the list elements
*/
protected TypeExpr getListElementType() {
// Assume: the type expression contained in the value node is the list constructor
return getValueNode().getTypeExpr().rootTypeConsApp().getArg(0);
}
/**
* {@inheritDoc}
*/
@Override
public void replaceValueNode(ValueNode newValueNode, boolean preserveInfo) {
super.replaceValueNode(newValueNode, preserveInfo);
// Update the UI
updateIconToolTip();
enableButtonsForTableState();
}
}