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.sheet;
022
023import java.awt.Color;
024import java.awt.Component;
025import java.awt.Dimension;
026import java.awt.Graphics;
027import java.awt.event.KeyEvent;
028import java.awt.event.MouseAdapter;
029import java.awt.event.MouseEvent;
030
031import javax.swing.Icon;
032import javax.swing.InputMap;
033import javax.swing.JButton;
034import javax.swing.JComponent;
035import javax.swing.JPanel;
036import javax.swing.JPopupMenu;
037import javax.swing.JScrollPane;
038import javax.swing.JTabbedPane;
039import javax.swing.KeyStroke;
040import javax.swing.SwingConstants;
041import javax.swing.UIManager;
042import javax.swing.event.ChangeEvent;
043import javax.swing.event.ChangeListener;
044import javax.swing.plaf.basic.BasicArrowButton;
045
046import csheets.core.Address;
047import csheets.core.Spreadsheet;
048import csheets.core.Workbook;
049import csheets.core.WorkbookListener;
050import csheets.ui.ctrl.ActionManager;
051import csheets.ui.ctrl.SelectionEvent;
052import csheets.ui.ctrl.SelectionListener;
053import csheets.ui.ctrl.UIController;
054
055/**
056 * A tabbed pane, used to display the spreadsheets in a workbook.
057 * @author Einar Pehrson
058 * @author Nobuo Tamemasa
059 */
060@SuppressWarnings("serial")
061public class WorkbookPane extends JTabbedPane implements SelectionListener {
062
063        /** The command for navigating to the first tab in the pane */
064        public static final String FIRST_COMMAND = "First tab";
065
066        /** The command for navigating to the previous tab in the pane */
067        public static final String PREV_COMMAND = "Previous tab";
068
069        /** The command for navigating to the next tab in the pane */
070        public static final String NEXT_COMMAND = "Next tab";
071
072        /** The command for navigating to the last tab in the pane */
073        public static final String LAST_COMMAND = "Last tab";
074
075        /** The user interface controller */
076        private UIController uiController;
077
078        /** The navigation buttons */
079        private JButton[] buttons = new JButton[] {
080                new StopArrowButton(WEST, FIRST_COMMAND),
081                new BasicArrowButton(WEST),
082                new BasicArrowButton(EAST),
083                new StopArrowButton(EAST, LAST_COMMAND)
084        };
085
086        /** The preferred size of the navigation buttons */
087        private Dimension buttonSize = new Dimension(16,17);
088
089        /** The number of visible tabs in the pane */
090        private int visibleCount = 0;
091
092        /** The index of the fist visible tab in the pane */
093        private int visibleStartIndex = 0;
094
095        /** The popup-menu */
096        private JPopupMenu popupMenu = new JPopupMenu();
097
098        /** The workbook listener that manages spreadsheets in the pane */
099        private WorkbookListener synchronizer = new SpreadsheetSynchronizer();
100
101        /**
102         * Creates a workbook pane.
103         * @param actionManager a manager for actions
104         * @param uiController the user interface controller
105         */
106        public WorkbookPane(UIController uiController, ActionManager actionManager) {
107                super(BOTTOM, SCROLL_TAB_LAYOUT);
108
109                // Stores members
110                this.uiController = uiController;
111                uiController.addSelectionListener(this);
112
113                // Configures navigation
114                WorkbookPaneUI ui = new WorkbookPaneUI();
115                buttons[1].setActionCommand(PREV_COMMAND);
116                buttons[2].setActionCommand(NEXT_COMMAND);
117                setUI(ui);
118
119                // Configures actions
120                InputMap inputMap = getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
121                inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_UP, KeyEvent.CTRL_MASK),
122                        "Select previous spreadsheet");
123                inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN, KeyEvent.CTRL_MASK),
124                        "Select next spreadsheet");
125                getActionMap().put("Select previous spreadsheet", ui.new NavigateAction(SwingConstants.PREVIOUS));
126                getActionMap().put("Select next spreadsheet", ui.new NavigateAction(SwingConstants.NEXT));
127
128                // Adds popup-menu
129                popupMenu.add(actionManager.getAction("addsheet"));
130                popupMenu.add(actionManager.getAction("removesheet"));
131                popupMenu.add(actionManager.getAction("renamesheet"));
132                addMouseListener(new PopupShower());            
133        }
134
135/*
136 * SELECTION
137 */
138
139        /**
140         * Updates the tabs in the pane when a new active workbook is selected.
141         * @param event the selection event that was fired
142         */
143        public void selectionChanged(SelectionEvent event) {
144                Workbook workbook = event.getWorkbook();
145                if (event.isWorkbookChanged()) {
146                        // Adds spreadsheet tables
147                        removeAll();
148                        if (workbook != null && workbook.getSpreadsheetCount() > 0) {
149                                int i = 0;
150                                for (Spreadsheet spreadsheet : workbook) {
151                                        SpreadsheetTable table = new SpreadsheetTable(spreadsheet, uiController);
152                                        add(spreadsheet.getTitle(), new JScrollPane(table));
153                                        setMnemonicAt(i++, KeyStroke.getKeyStroke(Integer.toString(i)).getKeyCode());
154                                        table.selectionChanged(event);
155                                }
156                        } else
157                                add(new JPanel());
158
159                        // Adds listener
160                        if (event.getPreviousWorkbook() != null)
161                                event.getPreviousWorkbook().removeWorkbookListener(synchronizer);
162                        if (event.getWorkbook() != null)
163                                event.getWorkbook().addWorkbookListener(synchronizer);
164                }
165        }
166
167        protected ChangeListener createChangeListener() {
168                return new SelectionListener();
169        }
170
171        /**
172         * An extension of the change listener added to the tabbed pane's list
173         * selection model, which also updates the <code>SelectionController</code.
174         */
175        @SuppressWarnings("serial")
176        protected class SelectionListener extends ModelListener {
177
178                public void stateChanged(ChangeEvent e) {
179                        super.stateChanged(e);
180
181                        // Updates selection
182                        Component selected = getSelectedComponent();
183                        if (selected != null && selected instanceof JScrollPane) {
184                                Component c = ((JScrollPane)selected).getViewport().getView();
185                                if (c instanceof SpreadsheetTable) {
186                                        SpreadsheetTable table = (SpreadsheetTable)c;
187                                        int activeColumn = table.getColumnModel().getSelectionModel().getAnchorSelectionIndex();
188                                        int activeRow = table.getSelectionModel().getAnchorSelectionIndex();
189                                        uiController.setActiveCell(table.getSpreadsheet()
190                                                .getCell(new Address(activeColumn, activeRow)));
191                                }
192                        }
193                }
194        }
195
196/*
197 * UPDATES
198 */
199
200        /**
201         * A workbook listener that adds and removes spreadsheets in the pane.
202         */
203        private class SpreadsheetSynchronizer implements WorkbookListener {
204
205                public void spreadsheetInserted(Spreadsheet spreadsheet, int index) {
206                        insertTab(spreadsheet.getTitle(), null, new JScrollPane(
207                                new SpreadsheetTable(spreadsheet, uiController)), null, index);
208                        for (int i = 0; i < getTabCount(); i++)
209                                setMnemonicAt(i, KeyStroke.getKeyStroke(Integer.toString(i)).getKeyCode());
210                }
211        
212                public void spreadsheetRemoved(Spreadsheet spreadsheet) {
213                        for (Component c : getComponents())
214                                if (c instanceof JScrollPane) {
215                                        Component view = ((JScrollPane)c).getViewport().getView();
216                                        if (view instanceof SpreadsheetTable)
217                                                if (((SpreadsheetTable)view).getSpreadsheet() == spreadsheet)
218                                                        remove(c);
219                                }
220                }
221        
222                public void spreadsheetRenamed(Spreadsheet spreadsheet) {}
223        }
224
225/*
226 * POPUP MENU
227 */
228
229        /**
230         * A mouse listener that shows a pop-up menu whenever appropriate.
231         */
232        private class PopupShower extends MouseAdapter {
233
234                public void mousePressed(MouseEvent e) {
235                        maybeShowPopup(e);
236                }
237
238                public void mouseReleased(MouseEvent e) {
239                        maybeShowPopup(e);
240                }
241
242                public void maybeShowPopup(MouseEvent e) {
243                        if (e.isPopupTrigger())
244                                popupMenu.show(e.getComponent(), e.getX(),
245                                        e.getY() - popupMenu.getPreferredSize().height);
246                }
247        }
248
249/*
250 * NAVIGATION
251 */
252
253        public Dimension getPreferredButtonSize() {
254                return buttonSize;
255        }
256
257        public JButton[] getButtons() {
258                return buttons;
259        }
260
261        public void insertTab(String title, Icon icon, Component component,
262                        String tip, int index) {
263                if (component instanceof BasicArrowButton) {
264                        if (component != null) {
265                                component.setVisible(true);
266                                addImpl(component, null, -1);
267                        }
268                } else
269                        super.insertTab(title, icon, component, tip, index);
270        }
271
272
273        public boolean isVisibleTab(int index) {
274                if ((visibleStartIndex <= index) &&
275                                (index < visibleStartIndex + visibleCount)) {
276                        return true;
277                } else
278                        return false;
279        }
280
281        public int getVisibleCount() {
282                return visibleCount;
283        }
284
285        public void setVisibleCount(int visibleCount) {
286                if (visibleCount < 0)
287                        return;
288                this.visibleCount = visibleCount;
289        }
290
291        public int getVisibleStartIndex() {
292                return visibleStartIndex;
293        }
294
295        public void setVisibleStartIndex(int visibleStartIndex) {
296                if (visibleStartIndex < 0 || getTabCount() <= visibleStartIndex)
297                        return;
298                this.visibleStartIndex = visibleStartIndex;
299        }
300
301        /**
302         * An extension of a <code>BasicArrowButton</code> that adds a stop dash
303         * to the button.
304         * @author Nobuo Tamemasa
305         * @author Einar Pehrson
306         */
307        @SuppressWarnings("serial")
308        protected static class StopArrowButton extends BasicArrowButton {
309        
310                /**
311                 * Creates a new stop arrow button.
312                 * @param direction the direction in which the button's arrow faces
313                 */
314                public StopArrowButton(int direction, String command) {
315                        super(direction);
316                        setActionCommand(command);
317                }
318        
319                public void paintTriangle(Graphics g, int x, int y, int size, 
320                                                int direction, boolean isEnabled) {
321                        super.paintTriangle(g, x, y, size, direction, isEnabled);
322                        Color c = g.getColor();
323                        if (isEnabled)
324                                g.setColor(UIManager.getColor("controlDkShadow"));
325                        else
326                                g.setColor(UIManager.getColor("controlShadow"));
327                        g.translate(x, y);
328                        size = Math.max(size, 2);
329                        int mid = size / 2;
330                        int h = size-1;
331                        if (direction == WEST) {
332                                g.drawLine(-1, mid-h, -1, mid+h);
333                                if (!isEnabled) {
334                                        g.setColor(UIManager.getColor("controlLtHighlight"));
335                                        g.drawLine(0, mid-h+1, 0, mid-1);
336                                        g.drawLine(0, mid+2, 0, mid+h+1);
337                                }
338                        } else {
339                                g.drawLine(size, mid-h, size, mid+h);
340                                if (!isEnabled) {
341                                        g.setColor(UIManager.getColor("controlLtHighlight"));
342                                        g.drawLine(size+1, mid-h+1, size+1, mid+h+1);
343                                }
344                        }       
345                        g.setColor(c);                  
346                }
347        }
348}