001/*
002 * Copyright (c) 2005 Einar Pehrson <einar@pehrson.nu>.
003 *
004 * This file is part of
005 * CleanSheets - a spreadsheet application for the Java platform.
006 *
007 * CleanSheets is free software; you can redistribute it and/or modify
008 * it under the terms of the GNU General Public License as published by
009 * the Free Software Foundation; either version 2 of the License, or
010 * (at your option) any later version.
011 *
012 * CleanSheets is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
015 * GNU General Public License for more details.
016 *
017 * You should have received a copy of the GNU General Public License
018 * along with CleanSheets; if not, write to the Free Software
019 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
020 */
021package csheets.ui.grid;
022
023import java.awt.Color;
024import java.awt.event.ActionEvent;
025import java.awt.event.KeyEvent;
026
027import javax.swing.AbstractAction;
028import javax.swing.JComponent;
029import javax.swing.JScrollPane;
030import javax.swing.JTable;
031import javax.swing.JViewport;
032import javax.swing.KeyStroke;
033import javax.swing.ListSelectionModel;
034import javax.swing.SwingConstants;
035import javax.swing.UIManager;
036import javax.swing.table.TableModel;
037
038
039/**
040 * A customized JTable component, with a row header and some other improved
041 * features.
042 * @author Einar Pehrson
043 */
044@SuppressWarnings("serial")
045public class Grid extends JTable {
046
047        /** The action command used for the action */
048        public static final String RESUME_EDIT_COMMAND = "Edit active cell";
049
050        /** The table's row header, if it has been placed in a scroll bar */
051        private RowHeader rowHeader;
052
053        /**
054         * Creates a blank grid.
055         */
056        public Grid() {
057                this(null);
058        }
059
060        /**
061         * Creates a grid for the given table model.
062         * @param tableModel the table model to display in the table
063         */
064        public Grid(TableModel tableModel) {
065                super(tableModel);
066
067                // Configures reordering and resizing
068                getTableHeader().setReorderingAllowed(false);
069                getTableHeader().setResizingAllowed(true);
070                setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
071
072                // Configures selection
073                setCellSelectionEnabled(true);
074                setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);
075
076                // Configures cell borders
077                setGridColor(gridColor.brighter());
078                // setShowGrid(false);
079                // setIntercellSpacing(new Dimension(0, 0));
080
081                // Configures table headers
082                UIManager.getDefaults().putDefaults(new Object[] {
083                        "TableHeader.selectionForeground", Color.black,
084                        "TableHeader.selectionBackground", Color.orange});
085                getTableHeader().setDefaultRenderer(
086                        new HeaderRenderer(SwingConstants.HORIZONTAL));
087
088                // Configures cell editing
089                setSurrendersFocusOnKeystroke(true);
090                getActionMap().put(RESUME_EDIT_COMMAND, new ResumeEditAction());
091                getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_F2, 0), RESUME_EDIT_COMMAND);
092        }
093
094        /**
095         * Processes key bindings in the table. Overridden to prevent modified
096         * key events from invoking the editor.
097         */
098        protected boolean processKeyBinding(KeyStroke ks, KeyEvent e, int condition,
099                        boolean pressed) {
100                if (e.getKeyCode() == KeyEvent.VK_C
101                 || e.getKeyCode() == KeyEvent.VK_X
102                 || e.getKeyCode() == KeyEvent.VK_V
103                 || !(e.isAltDown() || e.isControlDown()))
104                        return super.processKeyBinding(ks, e, condition, pressed);
105                else
106                        return false;
107        }
108
109        /**
110         * Changes the current selection in the table. Overridden to repaint row
111         * and column headers as well.
112         * @param row the row that was selected
113         * @param column the column that was selected
114         * @param toggle whether the selection should be toggled
115         * @param extend whether the selection should be extended
116         */
117        public void changeSelection(int row, int column, boolean toggle, boolean extend) {
118                super.changeSelection(row, column, toggle, extend);
119                if (tableHeader != null)
120                        tableHeader.repaint();
121                if (rowHeader != null)
122                        rowHeader.repaint();
123        }
124
125/*
126 * ROW HEADER
127 */
128
129        /**
130         * Adds the row header.
131         */
132        protected void configureEnclosingScrollPane() {
133                super.configureEnclosingScrollPane();
134                if (rowHeader == null)
135                        rowHeader = new RowHeader(this);
136                setEnclosingScrollPaneRowHeaderView(rowHeader);
137        }
138
139        /**
140         * Removes the row header.
141         */
142        protected void unconfigureEnclosingScrollPane() {
143                super.unconfigureEnclosingScrollPane();
144                setEnclosingScrollPaneRowHeaderView(null);
145        }
146
147        /**
148         * Sets the row header view of an enclosing scroll pane to the given
149         * component.
150         * @param header the component to use as the row header, or null if no header is wanted
151         */
152        private void setEnclosingScrollPaneRowHeaderView(JComponent header) {
153                // If the table is the main viewport of a scroll pane
154                if (getParent() instanceof JViewport)
155                        if (getParent().getParent() instanceof JScrollPane) {
156                                JScrollPane scrollPane = (JScrollPane)(getParent().getParent());
157                                JViewport viewport = scrollPane.getViewport();
158                                if (viewport != null && viewport.getView() == this)
159                                        // Updates row header
160                                        scrollPane.setRowHeaderView(header);
161                        }
162        }
163
164/*
165 * ACTIONS
166 */
167
168        /**
169         * An action for editing a cell, without clearing its contents.
170         * @author Einar Pehrson
171         */
172        protected class ResumeEditAction extends AbstractAction {
173
174                /**
175                 * Creates an edit resuming action.
176                 */
177                public ResumeEditAction() {
178                        // Configures action
179                        putValue(NAME, RESUME_EDIT_COMMAND);
180                        putValue(SHORT_DESCRIPTION, RESUME_EDIT_COMMAND);
181                        putValue(ACTION_COMMAND_KEY, RESUME_EDIT_COMMAND);
182                }
183
184                public void actionPerformed(ActionEvent e) {
185                        int row = getSelectionModel().getAnchorSelectionIndex();
186                        int column = getColumnModel().getSelectionModel().getAnchorSelectionIndex();
187                        if (row >= 0 && column >= 0)
188                                editCellAt(row, column, e);
189                }
190        }
191}