001 /*
002 * Copyright (c) 2005 Einar Pehrson <einar@pehrson.nu>.
003 * Copyright (c) Nobuo Tamemasa
004 *
005 * This file is part of
006 * CleanSheets - a spreadsheet application for the Java platform.
007 *
008 * CleanSheets is free software; you can redistribute it and/or modify
009 * it under the terms of the GNU General Public License as published by
010 * the Free Software Foundation; either version 2 of the License, or
011 * (at your option) any later version.
012 *
013 * CleanSheets is distributed in the hope that it will be useful,
014 * but WITHOUT ANY WARRANTY; without even the implied warranty of
015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
016 * GNU General Public License for more details.
017 *
018 * You should have received a copy of the GNU General Public License
019 * along with CleanSheets; if not, write to the Free Software
020 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
021 */
022 package csheets.ui.sheet;
023
024 import java.awt.Container;
025 import java.awt.Dimension;
026 import java.awt.Font;
027 import java.awt.FontMetrics;
028 import java.awt.Graphics;
029 import java.awt.Insets;
030 import java.awt.LayoutManager;
031 import java.awt.Rectangle;
032 import java.awt.event.ActionEvent;
033 import java.awt.event.ActionListener;
034
035 import javax.swing.AbstractAction;
036 import javax.swing.JButton;
037 import javax.swing.JComponent;
038 import javax.swing.JTabbedPane;
039 import javax.swing.SwingConstants;
040 import javax.swing.plaf.basic.BasicTabbedPaneUI;
041 import javax.swing.plaf.metal.MetalTabbedPaneUI;
042
043 /**
044 * An extension of the <code>MetalTabbedPaneUI</code> that adds a number of
045 * navigation buttons to the pane.
046 * @author Nobuo Tamemasa
047 * @author Einar Pehrson
048 */
049 public class WorkbookPaneUI extends MetalTabbedPaneUI {
050
051 protected ActionListener[] buttonListeners;
052
053 /**
054 * Creates a new WorkbookPane UI.
055 */
056 public WorkbookPaneUI() {}
057
058 public void installUI(JComponent c) {
059 this.tabPane = (JTabbedPane)c;
060 c.setLayout(createLayoutManager());
061 installDefaults();
062 installComponents();
063 installListeners();
064 installKeyboardActions();
065 runCount = 1;
066 selectedRun = 0;
067 }
068
069 public void uninstallUI(JComponent c) {
070 uninstallComponents();
071 super.uninstallUI(c);
072 }
073
074 protected LayoutManager createLayoutManager() {
075 return new SingleRowTabbedLayout(tabPane);
076 }
077
078 protected void installComponents() {
079 JButton[] buttons = ((WorkbookPane)tabPane).getButtons();
080 for (int i=0;i<buttons.length;i++) {
081 tabPane.add(buttons[i]);
082 }
083 }
084
085 protected void uninstallComponents() {
086 JButton[] buttons = ((WorkbookPane)tabPane).getButtons();
087 for (int i=0;i<buttons.length;i++) {
088 tabPane.remove(buttons[i]);
089 }
090 }
091
092 protected void installListeners() {
093 super.installListeners();
094 WorkbookPane stabPane = (WorkbookPane)tabPane;
095 JButton[] buttons = stabPane.getButtons();
096 int n = buttons.length;
097 buttonListeners = new ActionListener[n];
098
099 for (int i=0;i<n;i++) {
100 buttonListeners[i] = null;
101 String str = buttons[i].getActionCommand();
102
103 if (str.equals(WorkbookPane.FIRST_COMMAND)) {
104 buttonListeners[i] = new TabShifter();
105 } else if (str.equals(WorkbookPane.PREV_COMMAND)) {
106 buttonListeners[i] = new TabShifter() {
107 protected int getStartIndex() {
108 return sPane.getVisibleStartIndex() - 1;
109 }
110 };
111 } else if (str.equals(WorkbookPane.NEXT_COMMAND)) {
112 buttonListeners[i] = new TabShifter() {
113 protected int getStartIndex() {
114 return sPane.getVisibleStartIndex() + 1;
115 }
116 };
117 } else if (str.equals(WorkbookPane.LAST_COMMAND)) {
118 buttonListeners[i] = new TabShifter() {
119 protected int getStartIndex() {
120 return getStartIndex(sPane.getTabCount() - 1);
121 }
122 };
123 }
124 buttons[i].addActionListener(buttonListeners[i]);
125 }
126 }
127
128 protected void uninstallListeners() {
129 super.uninstallListeners();
130 JButton[] buttons = ((WorkbookPane)tabPane).getButtons();
131 for (int i=0;i<buttons.length;i++) {
132 buttons[i].removeActionListener(buttonListeners[i]);
133 }
134 }
135
136 public int tabForCoordinate(JTabbedPane pane, int x, int y) {
137 WorkbookPane stabPane = (WorkbookPane)tabPane;
138 int visibleCount = stabPane.getVisibleCount();
139 int visibleStartIndex = stabPane.getVisibleStartIndex();
140
141 for (int i=0,index = visibleStartIndex; i < visibleCount
142 && i < rects.length; i++,index++) {
143 if (rects[index].contains(x, y)) {
144 return index;
145 }
146 }
147 return -1;
148 }
149
150 public void paint(Graphics g, JComponent c) {
151 int selectedIndex = tabPane.getSelectedIndex();
152 int tabPlacement = tabPane.getTabPlacement();
153 ensureCurrentLayout();
154
155 WorkbookPane stabPane = (WorkbookPane)tabPane;
156 int visibleCount = stabPane.getVisibleCount();
157 int visibleStartIndex = stabPane.getVisibleStartIndex();
158
159 Rectangle iconRect = new Rectangle(),
160 textRect = new Rectangle();
161 Rectangle clipRect = g.getClipBounds();
162 tabRuns[0] = visibleStartIndex;
163
164 for (int i=0,index=visibleStartIndex; i<visibleCount && i<rects.length; i++,index++) {
165 if (rects[index].intersects(clipRect)) {
166 paintTab(g, tabPlacement, rects, index, iconRect, textRect);
167 }
168 }
169 if (stabPane.isVisibleTab(selectedIndex)) {
170 if (rects[selectedIndex].intersects(clipRect)) {
171 paintTab(g, tabPlacement, rects, selectedIndex, iconRect, textRect);
172 }
173 }
174
175 paintContentBorder(g, tabPlacement, selectedIndex);
176 }
177
178
179 protected void paintContentBorderTopEdge( Graphics g,
180 int tabPlacement, int selectedIndex, int x, int y, int w, int h ) {
181 g.setColor(selectHighlight);
182 if (tabPlacement != TOP || selectedIndex < 0 ||
183 (rects[selectedIndex].y + rects[selectedIndex].height + 1 < y) ||
184 !((WorkbookPane)tabPane).isVisibleTab(selectedIndex) ) {
185 g.drawLine(x, y, x+w-2, y);
186 } else {
187 Rectangle selRect = rects[selectedIndex];
188 g.drawLine(x, y, selRect.x + 1, y);
189 if (selRect.x + selRect.width < x + w - 2) {
190 g.drawLine(selRect.x + selRect.width, y, x+w-2, y);
191 } else {
192 g.setColor(shadow);
193 g.drawLine(x+w-2, y, x+w-2, y);
194 }
195 }
196 }
197
198 protected void paintContentBorderBottomEdge(Graphics g,
199 int tabPlacement, int selectedIndex, int x, int y, int w, int h) {
200 g.setColor(darkShadow);
201 if (tabPlacement != BOTTOM || selectedIndex < 0 ||
202 (rects[selectedIndex].y - 1 > h) ||
203 !((WorkbookPane)tabPane).isVisibleTab(selectedIndex) ) {
204 g.drawLine(x, y+h-1, x+w-1, y+h-1);
205 } else {
206 Rectangle selRect = rects[selectedIndex];
207 g.drawLine(x, y+h-1, selRect.x, y+h-1);
208 if (selRect.x + selRect.width < x + w - 2) {
209 g.drawLine(selRect.x + selRect.width, y+h-1, x+w-1, y+h-1);
210 }
211 }
212 }
213
214
215
216 protected Insets getTabAreaInsets(int tabPlacement) {
217 WorkbookPane stabPane = (WorkbookPane)tabPane;
218 Dimension d = stabPane.getPreferredButtonSize();
219 int n = 4;
220 Insets currentInsets = new Insets(0,0,0,0);
221 currentInsets.top = tabAreaInsets.bottom;
222 currentInsets.bottom = tabAreaInsets.top;
223 currentInsets.left = tabAreaInsets.left + n * d.width;
224 currentInsets.right = tabAreaInsets.right;
225 return currentInsets;
226 }
227
228 protected int lastTabInRun(int tabCount, int run) {
229 WorkbookPane stabPane = (WorkbookPane)tabPane;
230 return stabPane.getVisibleStartIndex() + stabPane.getVisibleCount() -1;
231 }
232
233 protected void ensureCurrentLayout() {
234 SingleRowTabbedLayout layout = (SingleRowTabbedLayout)tabPane.getLayout();
235 layout.calculateLayoutInfo();
236 setButtonsEnabled();
237 }
238
239 protected void setButtonsEnabled() {
240 WorkbookPane stabPane = (WorkbookPane)tabPane;
241 int visibleCount = stabPane.getVisibleCount();
242 int visibleStartIndex = stabPane.getVisibleStartIndex();
243 JButton[] buttons = stabPane.getButtons();
244 boolean lEnable = 0 < visibleStartIndex;
245 boolean rEnable = visibleStartIndex + visibleCount < tabPane.getTabCount();
246 for (int i=0;i<buttons.length;i++) {
247 boolean enable = false;
248 String str = buttons[i].getActionCommand();
249 if (str.equals(WorkbookPane.FIRST_COMMAND))
250 enable = lEnable;
251 else if (str.equals(WorkbookPane.PREV_COMMAND))
252 enable = lEnable;
253 else if (str.equals(WorkbookPane.NEXT_COMMAND))
254 enable = rEnable;
255 else if (str.equals(WorkbookPane.LAST_COMMAND))
256 enable = rEnable;
257 buttons[i].setEnabled(enable);
258 }
259 }
260
261 //
262 // Tab Navigation by Key
263 // (Not yet done)
264 //
265 protected void ensureVisibleTabAt(int index) {
266 WorkbookPane stabPane = (WorkbookPane)tabPane;
267 int visibleCount = stabPane.getVisibleCount();
268 int visibleStartIndex = stabPane.getVisibleStartIndex();
269 int visibleEndIndex = visibleStartIndex + visibleCount -1;
270
271 if (visibleStartIndex < index && index < visibleEndIndex) {
272 return;
273 }
274 // int selectedIndex = tabPane.getSelectedIndex();
275 // boolean directionIsRight = (0 < index - selectedIndex)? true: false;
276 //if (directionIsRight) {
277 if (index <= visibleStartIndex) {
278 //System.out.println("dec");
279 if (visibleStartIndex == 0) return;
280 stabPane.setVisibleStartIndex( --visibleStartIndex );
281 ((SingleRowTabbedLayout)tabPane.getLayout()).calculateLayoutInfo();
282 int count = stabPane.getVisibleCount();
283 int startIndex = stabPane.getVisibleStartIndex();
284 if (startIndex <= index &&
285 index <= startIndex + count-1) {
286 } else {
287 stabPane.setVisibleStartIndex( ++visibleStartIndex );
288 }
289 }
290 //} else {
291 if (visibleEndIndex <= index) {
292 if (visibleStartIndex == visibleCount+1) return;
293 stabPane.setVisibleStartIndex( ++visibleStartIndex );
294 ((SingleRowTabbedLayout)tabPane.getLayout()).calculateLayoutInfo();
295 int count = stabPane.getVisibleCount();
296 int startIndex = stabPane.getVisibleStartIndex();
297 if (startIndex <= index &&
298 index <= startIndex + count-1) {
299 } else {
300 stabPane.setVisibleStartIndex( --visibleStartIndex );
301 }
302 }
303 //}
304 }
305
306 protected void selectNextTab(int current) {
307 for (int i=current+1;i<tabPane.getTabCount();i++) {
308 if (tabPane.isEnabledAt(i)) {
309 ensureVisibleTabAt(i);
310 tabPane.setSelectedIndex(i);
311 break;
312 }
313 }
314 }
315
316 protected void selectPreviousTab(int current) {
317 for (int i=current-1;0<=i;i--) {
318 if (tabPane.isEnabledAt(i)) {
319 ensureVisibleTabAt(i);
320 tabPane.setSelectedIndex(i);
321 break;
322 }
323 }
324 }
325
326 // these methods exist for innerclass
327 void setMaxTabHeight(int maxTabHeight) {
328 this.maxTabHeight = maxTabHeight;
329 }
330
331 int getMaxTabHeight() {
332 return maxTabHeight;
333 }
334
335 Rectangle[] getRects() {
336 return rects;
337 }
338
339 WorkbookPane getTabbedPane() {
340 return (WorkbookPane)tabPane;
341 }
342
343 protected FontMetrics getFontMetrics() {
344 Font font = tabPane.getFont();
345 return tabPane.getFontMetrics(font);
346 }
347
348 protected int calculateMaxTabHeight(int tabPlacement) {
349 return super.calculateMaxTabHeight(tabPlacement);
350 }
351
352 protected int calculateTabWidth(int tabPlacement, int tabIndex, FontMetrics metrics) {
353 return super.calculateTabWidth(tabPlacement, tabIndex, metrics);
354 }
355
356 protected void assureRectsCreated(int tabCount) {
357 super.assureRectsCreated(tabCount);
358 }
359
360 /**
361 * The layout used for the tabbed pane.
362 */
363 protected class SingleRowTabbedLayout extends BasicTabbedPaneUI.TabbedPaneLayout {
364 JTabbedPane tabPane;
365
366 public SingleRowTabbedLayout(JTabbedPane tabPane) {
367 this.tabPane = tabPane;
368 }
369
370 public void layoutContainer(Container parent) {
371 super.layoutContainer(parent);
372 if (tabPane.getComponentCount() < 1) {
373 return;
374 }
375
376 int tabPlacement = tabPane.getTabPlacement();
377 int maxTabHeight = calculateMaxTabHeight(tabPlacement);
378 Insets tabAreaInsets = getTabAreaInsets(tabPlacement);
379 Insets insets = tabPane.getInsets();
380 Rectangle bounds = tabPane.getBounds();
381
382 WorkbookPane stabPane = (WorkbookPane)tabPane;
383 Dimension d = stabPane.getPreferredButtonSize();
384 JButton[] buttons = stabPane.getButtons();
385 int x,y;
386 y = bounds.y + bounds.height - insets.bottom
387 - tabAreaInsets.bottom - maxTabHeight;
388 x = bounds.x + insets.left;
389 for (int i=0;i<buttons.length;i++) {
390 buttons[i].setBounds(x, y, d.width, d.height);
391 x += d.width;
392 }
393 }
394
395 public void calculateLayoutInfo() {
396 int tabCount = tabPane.getTabCount();
397 assureRectsCreated(tabCount);
398 calculateTabWidths(tabPane.getTabPlacement(), tabCount);
399 calculateTabRects(tabPane.getTabPlacement(), tabCount);
400 }
401
402 protected void calculateTabWidths(int tabPlacement, int tabCount) {
403 if (tabCount == 0) {
404 return;
405 }
406 FontMetrics metrics = getFontMetrics();
407 int maxTabHeight = calculateMaxTabHeight(tabPlacement);
408 setMaxTabHeight(maxTabHeight);
409 Rectangle[] rects = getRects();
410 for (int i = 0; i < tabCount; i++) {
411 rects[i].width = calculateTabWidth(tabPlacement, i, metrics);
412 rects[i].height = maxTabHeight;
413 }
414 }
415
416 protected void calculateTabRects(int tabPlacement, int tabCount) {
417 if (tabCount == 0) {
418 return;
419 }
420 WorkbookPane stabPane = (WorkbookPane)tabPane;
421 Dimension size = tabPane.getSize();
422 Insets insets = tabPane.getInsets();
423 Insets tabAreaInsets = getTabAreaInsets(tabPlacement);
424 int maxTabHeight = getMaxTabHeight();
425 int x = insets.left + tabAreaInsets.left;
426 int y;
427 if (tabPlacement == TOP) {
428 y = insets.top + tabAreaInsets.top;
429 } else { // BOTTOM
430 y = size.height - insets.bottom - tabAreaInsets.bottom - maxTabHeight;
431 }
432
433 int returnAt = size.width - (insets.right + tabAreaInsets.right);
434 Rectangle[] rects = getRects();
435 int visibleStartIndex = stabPane.getVisibleStartIndex();
436 int visibleCount = 0;
437
438 for (int i = visibleStartIndex; i < tabCount; i++) {
439 Rectangle rect = rects[i];
440 if (visibleStartIndex < i) {
441 rect.x = rects[i-1].x + rects[i-1].width;
442 } else {
443 rect.x = x;
444 }
445
446 if (rect.x + rect.width > returnAt) {
447 break;
448 } else {
449 visibleCount++;
450 rect.y = y;
451 }
452 }
453 stabPane.setVisibleCount(visibleCount);
454 stabPane.setVisibleStartIndex(visibleStartIndex);
455 }
456 }
457
458 // Listener
459 protected class TabShifter implements ActionListener {
460 WorkbookPane sPane;
461
462 public void actionPerformed(ActionEvent e) {
463 sPane = getTabbedPane();
464 int index = getStartIndex();
465 sPane.setVisibleStartIndex(index);
466 sPane.repaint();
467 }
468
469 //public abstract int getStartIndex();
470 protected int getStartIndex() {
471 return 0; // first tab
472 }
473
474 protected int getStartIndex(int lastIndex) {
475 Insets insets = sPane.getInsets();
476 Insets tabAreaInsets = getTabAreaInsets(sPane.getTabPlacement());
477 int width = sPane.getSize().width
478 - (insets.left + insets.right)
479 - (tabAreaInsets.left + tabAreaInsets.right);
480 int index;
481 Rectangle[] rects = getRects();
482 for (index=lastIndex;0<=index;index--) {
483 width -= rects[index].width;
484 if (width < 0)
485 break;
486 }
487 return ++index;
488 }
489 }
490
491 /**
492 * An action for navigating between the tabs in the pane.
493 * @author Einar Pehrson
494 */
495 @SuppressWarnings("serial")
496 protected class NavigateAction extends AbstractAction {
497
498 /** The direction in which to navigate (a SwingConstants value) */
499 private int direction;
500
501 public NavigateAction(int direction) {
502 if (direction == SwingConstants.PREVIOUS
503 || direction == SwingConstants.NEXT)
504 this.direction = direction;
505 else
506 throw new IllegalArgumentException("A SwingConstants value expected");
507 }
508
509 public void actionPerformed(ActionEvent event) {
510 navigateSelectedTab(direction);
511 }
512 }
513 }