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     */
021    package csheets.ui.grid;
022    
023    import java.awt.Color;
024    import java.awt.event.ActionEvent;
025    import java.awt.event.KeyEvent;
026    
027    import javax.swing.AbstractAction;
028    import javax.swing.JComponent;
029    import javax.swing.JScrollPane;
030    import javax.swing.JTable;
031    import javax.swing.JViewport;
032    import javax.swing.KeyStroke;
033    import javax.swing.ListSelectionModel;
034    import javax.swing.SwingConstants;
035    import javax.swing.UIManager;
036    import 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")
045    public 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    }