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 */
022package csheets.ui.sheet;
023
024import java.awt.Container;
025import java.awt.Dimension;
026import java.awt.Font;
027import java.awt.FontMetrics;
028import java.awt.Graphics;
029import java.awt.Insets;
030import java.awt.LayoutManager;
031import java.awt.Rectangle;
032import java.awt.event.ActionEvent;
033import java.awt.event.ActionListener;
034
035import javax.swing.AbstractAction;
036import javax.swing.JButton;
037import javax.swing.JComponent;
038import javax.swing.JTabbedPane;
039import javax.swing.SwingConstants;
040import javax.swing.plaf.basic.BasicTabbedPaneUI;
041import 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 */
049public 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}