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.sheet;
022    
023    import java.awt.Color;
024    import java.awt.Component;
025    import java.awt.Dimension;
026    import java.awt.Graphics;
027    import java.awt.event.KeyEvent;
028    import java.awt.event.MouseAdapter;
029    import java.awt.event.MouseEvent;
030    
031    import javax.swing.Icon;
032    import javax.swing.InputMap;
033    import javax.swing.JButton;
034    import javax.swing.JComponent;
035    import javax.swing.JPanel;
036    import javax.swing.JPopupMenu;
037    import javax.swing.JScrollPane;
038    import javax.swing.JTabbedPane;
039    import javax.swing.KeyStroke;
040    import javax.swing.SwingConstants;
041    import javax.swing.UIManager;
042    import javax.swing.event.ChangeEvent;
043    import javax.swing.event.ChangeListener;
044    import javax.swing.plaf.basic.BasicArrowButton;
045    
046    import csheets.core.Address;
047    import csheets.core.Spreadsheet;
048    import csheets.core.Workbook;
049    import csheets.core.WorkbookListener;
050    import csheets.ui.ctrl.ActionManager;
051    import csheets.ui.ctrl.SelectionEvent;
052    import csheets.ui.ctrl.SelectionListener;
053    import 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")
061    public 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    }