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    }