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}