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 }