001/*
002 * $Id: BasicTaskPaneUI.java 4158 2012-02-03 18:29:40Z kschaefe $
003 *
004 * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
005 * Santa Clara, California 95054, U.S.A. All rights reserved.
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 * 
012 * This library 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 GNU
015 * Lesser General Public License for more details.
016 * 
017 * You should have received a copy of the GNU Lesser General Public
018 * License along with this library; if not, write to the Free Software
019 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
020 */
021package org.jdesktop.swingx.plaf.basic;
022
023import java.awt.Color;
024import java.awt.Component;
025import java.awt.Cursor;
026import java.awt.Dimension;
027import java.awt.Font;
028import java.awt.Graphics;
029import java.awt.Insets;
030import java.awt.Rectangle;
031import java.awt.event.ActionEvent;
032import java.awt.event.FocusEvent;
033import java.awt.event.FocusListener;
034import java.awt.event.MouseEvent;
035import java.beans.PropertyChangeEvent;
036import java.beans.PropertyChangeListener;
037
038import javax.swing.AbstractAction;
039import javax.swing.Action;
040import javax.swing.ActionMap;
041import javax.swing.BorderFactory;
042import javax.swing.Icon;
043import javax.swing.InputMap;
044import javax.swing.JComponent;
045import javax.swing.JLabel;
046import javax.swing.LookAndFeel;
047import javax.swing.SwingUtilities;
048import javax.swing.UIManager;
049import javax.swing.border.Border;
050import javax.swing.border.CompoundBorder;
051import javax.swing.event.MouseInputAdapter;
052import javax.swing.event.MouseInputListener;
053import javax.swing.plaf.ActionMapUIResource;
054import javax.swing.plaf.ColorUIResource;
055import javax.swing.plaf.ComponentUI;
056import javax.swing.plaf.FontUIResource;
057import javax.swing.plaf.UIResource;
058import javax.swing.plaf.basic.BasicGraphicsUtils;
059
060import org.jdesktop.swingx.JXCollapsiblePane;
061import org.jdesktop.swingx.JXHyperlink;
062import org.jdesktop.swingx.JXTaskPane;
063import org.jdesktop.swingx.SwingXUtilities;
064import org.jdesktop.swingx.icon.EmptyIcon;
065import org.jdesktop.swingx.plaf.TaskPaneUI;
066
067/**
068 * Base implementation of the <code>JXTaskPane</code> UI.
069 * 
070 * @author <a href="mailto:fred@L2FProd.com">Frederic Lavigne</a>
071 */
072public class BasicTaskPaneUI extends TaskPaneUI {
073
074    private static FocusListener focusListener = new RepaintOnFocus();
075
076    public static ComponentUI createUI(JComponent c) {
077        return new BasicTaskPaneUI();
078    }
079
080    protected int titleHeight = 25;
081    protected int roundHeight = 5;
082
083    protected JXTaskPane group;
084
085    protected boolean mouseOver;
086    protected MouseInputListener mouseListener;
087
088    protected PropertyChangeListener propertyListener;
089
090    /**
091     * {@inheritDoc}
092     */
093    @Override
094    public void installUI(JComponent c) {
095        super.installUI(c);
096        group = (JXTaskPane) c;
097
098        installDefaults();
099        installListeners();
100        installKeyboardActions();
101    }
102
103    /**
104     * Installs default properties. Following properties are installed:
105     * <ul>
106     * <li>TaskPane.background</li>
107     * <li>TaskPane.foreground</li>
108     * <li>TaskPane.font</li>
109     * <li>TaskPane.borderColor</li>
110     * <li>TaskPane.titleForeground</li>
111     * <li>TaskPane.titleBackgroundGradientStart</li>
112     * <li>TaskPane.titleBackgroundGradientEnd</li>
113     * <li>TaskPane.titleOver</li>
114     * <li>TaskPane.specialTitleOver</li>
115     * <li>TaskPane.specialTitleForeground</li>
116     * <li>TaskPane.specialTitleBackground</li>
117     * </ul>
118     */
119    protected void installDefaults() {
120        LookAndFeel.installProperty(group, "opaque", true);
121        group.setBorder(createPaneBorder());
122        ((JComponent) group.getContentPane())
123                .setBorder(createContentPaneBorder());
124
125        LookAndFeel.installColorsAndFont(group, "TaskPane.background",
126                "TaskPane.foreground", "TaskPane.font");
127
128        LookAndFeel.installColorsAndFont((JComponent) group.getContentPane(),
129                "TaskPane.background", "TaskPane.foreground", "TaskPane.font");
130    }
131
132    /**
133     * Installs listeners for UI delegate.
134     */
135    protected void installListeners() {
136        mouseListener = createMouseInputListener();
137        group.addMouseMotionListener(mouseListener);
138        group.addMouseListener(mouseListener);
139
140        group.addFocusListener(focusListener);
141        propertyListener = createPropertyListener();
142        group.addPropertyChangeListener(propertyListener);
143    }
144
145    /**
146     * Installs keyboard actions to allow task pane to react on hot keys.
147     */
148    protected void installKeyboardActions() {
149        InputMap inputMap = (InputMap) UIManager.get("TaskPane.focusInputMap");
150        if (inputMap != null) {
151            SwingUtilities.replaceUIInputMap(group, JComponent.WHEN_FOCUSED,
152                    inputMap);
153        }
154
155        ActionMap map = getActionMap();
156        if (map != null) {
157            SwingUtilities.replaceUIActionMap(group, map);
158        }
159    }
160
161    ActionMap getActionMap() {
162        ActionMap map = new ActionMapUIResource();
163        map.put("toggleCollapsed", new ToggleCollapsedAction());
164        return map;
165    }
166
167    @Override
168    public void uninstallUI(JComponent c) {
169        uninstallListeners();
170        super.uninstallUI(c);
171    }
172
173    /**
174     * Uninstalls previously installed listeners to free component for garbage collection. 
175     */
176    protected void uninstallListeners() {
177        group.removeMouseListener(mouseListener);
178        group.removeMouseMotionListener(mouseListener);
179        group.removeFocusListener(focusListener);
180        group.removePropertyChangeListener(propertyListener);
181    }
182
183    /**
184     * Creates new toggle listener.
185     * @return MouseInputListener reacting on toggle events of task pane.
186     */
187    protected MouseInputListener createMouseInputListener() {
188        return new ToggleListener();
189    }
190
191    /**
192     * Creates property change listener for task pane.
193     * @return Property change listener reacting on changes to the task pane.
194     */
195    protected PropertyChangeListener createPropertyListener() {
196        return new ChangeListener();
197    }
198
199    /**
200     * Evaluates whenever given mouse even have occurred within borders of task pane.
201     * @param event Evaluated event.
202     * @return True if event occurred within task pane area, false otherwise. 
203     */
204    protected boolean isInBorder(MouseEvent event) {
205        return event.getY() < getTitleHeight(event.getComponent());
206    }
207 
208        /**
209         * Gets current title height. Default value is 25 if not specified otherwise. Method checks 
210         * provided component for user set font (!instanceof FontUIResource), if font is set, height 
211         * will be calculated from font metrics instead of using internal preset height.
212         * @return Current title height.
213         */
214        protected int getTitleHeight(Component c) {
215            if (c instanceof JXTaskPane) {
216                JXTaskPane taskPane = (JXTaskPane) c;
217                Font font = taskPane.getFont();
218                int height = titleHeight;
219                
220                if (font != null && !(font instanceof FontUIResource)) {
221                    height = Math.max(height, taskPane.getFontMetrics(font).getHeight());
222                }
223                
224                Icon icon = taskPane.getIcon();
225                
226                if (icon != null) {
227                    height = Math.max(height, icon.getIconHeight() + 4);
228                }
229                
230                return height;
231            }
232            
233            return titleHeight;
234        }
235        
236    /**
237     * Creates new border for task pane.
238     * @return Fresh border on every call.
239     */
240    protected Border createPaneBorder() {
241        return new PaneBorder();
242    }
243
244    @Override
245    public Dimension getPreferredSize(JComponent c) {
246        Component component = group.getComponent(0);
247        if (!(component instanceof JXCollapsiblePane)) {
248            // something wrong in this JXTaskPane
249            return super.getPreferredSize(c);
250        }
251
252        JXCollapsiblePane collapsible = (JXCollapsiblePane) component;
253        Dimension dim = collapsible.getPreferredSize();
254
255        Border groupBorder = group.getBorder();
256        if (groupBorder instanceof PaneBorder) {
257            ((PaneBorder) groupBorder).label.setDisplayedMnemonic(group
258                    .getMnemonic());
259            Dimension border = ((PaneBorder) groupBorder)
260                    .getPreferredSize(group);
261            dim.width = Math.max(dim.width, border.width);
262            dim.height += border.height;
263        } else {
264            dim.height += getTitleHeight(c);
265        }
266
267        return dim;
268    }
269
270    /**
271     * Creates content pane border.
272     * @return Fresh content pane border initialized with current value of TaskPane.borderColor 
273     * on every call.
274     */
275    protected Border createContentPaneBorder() {
276        Color borderColor = UIManager.getColor("TaskPane.borderColor");
277        return new CompoundBorder(new ContentPaneBorder(borderColor),
278                BorderFactory.createEmptyBorder(10, 10, 10, 10));
279    }
280
281    @Override
282    public Component createAction(Action action) {
283        JXHyperlink link = new JXHyperlink(action) {
284            @Override
285            public void updateUI() {
286                super.updateUI();
287                // ensure the ui of this link is correctly update on l&f changes
288                configure(this);
289            }
290        };
291        configure(link);
292        return link;
293    }
294
295    /**
296     * Configures internally used hyperlink on new action creation and on every call to 
297     * <code>updateUI()</code>.
298     * @param link Configured hyperlink.
299     */
300    protected void configure(JXHyperlink link) {
301        link.setOpaque(false);
302        link.setBorderPainted(false);
303        link.setFocusPainted(true);
304        link.setForeground(UIManager.getColor("TaskPane.titleForeground"));
305    }
306
307    /**
308     * Ensures expanded group is visible. Issues delayed request for scrolling to visible.
309     */
310    protected void ensureVisible() {
311        SwingUtilities.invokeLater(new Runnable() {
312            @Override
313            public void run() {
314                group.scrollRectToVisible(new Rectangle(group.getWidth(), group
315                        .getHeight()));
316            }
317        });
318    }
319
320    /**
321     * Focus listener responsible for repainting of the taskpane on focus change.
322     */
323    static class RepaintOnFocus implements FocusListener {
324        @Override
325        public void focusGained(FocusEvent e) {
326            e.getComponent().repaint();
327        }
328
329        @Override
330        public void focusLost(FocusEvent e) {
331            e.getComponent().repaint();
332        }
333    }
334
335    /**
336     * Change listener responsible for change handling.
337     */
338    class ChangeListener implements PropertyChangeListener {
339        @Override
340        public void propertyChange(PropertyChangeEvent evt) {
341            // if group is expanded but not animated
342            // or if animated has reached expanded state
343            // scroll to visible if scrollOnExpand is enabled
344            if (("collapsed".equals(evt.getPropertyName())
345                    && Boolean.TRUE.equals(evt.getNewValue()) && !group
346                    .isAnimated())) {
347                if (group.isScrollOnExpand()) {
348                    ensureVisible();
349                }
350            } else if (JXTaskPane.ICON_CHANGED_KEY
351                    .equals(evt.getPropertyName())
352                    || JXTaskPane.TITLE_CHANGED_KEY.equals(evt
353                            .getPropertyName())
354                    || JXTaskPane.SPECIAL_CHANGED_KEY.equals(evt
355                            .getPropertyName())) {
356                // icon, title, special must lead to a repaint()
357                group.repaint();
358            } else if ("mnemonic".equals(evt.getPropertyName())) {
359                SwingXUtilities.updateMnemonicBinding(group, "toggleCollapsed");
360                
361                Border b = group.getBorder();
362                
363                if (b instanceof PaneBorder) {
364                    int key = (Integer) evt.getNewValue();
365                    ((PaneBorder) b).label.setDisplayedMnemonic(key);
366                }
367            }
368        }
369    }
370
371    /**
372     * Mouse listener responsible for handling of toggle events.
373     */
374    class ToggleListener extends MouseInputAdapter {
375        @Override
376        public void mouseEntered(MouseEvent e) {
377            if (isInBorder(e)) {
378                e.getComponent().setCursor(
379                        Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
380            } else {
381                mouseOver = false;
382                                group.repaint(0, 0, group.getWidth(), getTitleHeight(group));
383            }
384        }
385
386        @Override
387        public void mouseExited(MouseEvent e) {
388            e.getComponent().setCursor(null);
389            mouseOver = false;
390                        group.repaint(0, 0, group.getWidth(), getTitleHeight(group));
391        }
392
393        @Override
394        public void mouseMoved(MouseEvent e) {
395            if (isInBorder(e)) {
396                e.getComponent().setCursor(
397                        Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
398                mouseOver = true;
399            } else {
400                e.getComponent().setCursor(null);
401                mouseOver = false;
402            }
403            
404                        group.repaint(0, 0, group.getWidth(), getTitleHeight(group));
405        }
406
407        @Override
408        public void mouseReleased(MouseEvent e) {
409            if (SwingUtilities.isLeftMouseButton(e) && isInBorder(e)) {
410                group.setCollapsed(!group.isCollapsed());
411            }
412        }
413    }
414    
415    /**
416     * Toggle expanded action.
417     */
418    class ToggleCollapsedAction extends AbstractAction {
419        /**
420         * Serial version UID.
421         */
422        private static final long serialVersionUID = 5676859881615358815L;
423        
424        public ToggleCollapsedAction() {
425            super("toggleCollapsed");
426        }
427        
428        @Override
429        public void actionPerformed(ActionEvent e) {
430            group.setCollapsed(!group.isCollapsed());
431        }
432        
433        @Override
434        public boolean isEnabled() {
435            return group.isVisible();
436        }
437    }
438
439    /**
440     * Toggle icon.
441     */
442    protected static class ChevronIcon implements Icon {
443        boolean up = true;
444
445        public ChevronIcon(boolean up) {
446            this.up = up;
447        }
448
449        @Override
450        public int getIconHeight() {
451            return 3;
452        }
453
454        @Override
455        public int getIconWidth() {
456            return 6;
457        }
458
459        @Override
460        public void paintIcon(Component c, Graphics g, int x, int y) {
461            if (up) {
462                g.drawLine(x + 3, y, x, y + 3);
463                g.drawLine(x + 3, y, x + 6, y + 3);
464            } else {
465                g.drawLine(x, y, x + 3, y + 3);
466                g.drawLine(x + 3, y + 3, x + 6, y);
467            }
468        }
469    }
470
471    /**
472     * The border around the content pane
473     */
474    protected static class ContentPaneBorder implements Border, UIResource {
475        Color color;
476
477        public ContentPaneBorder(Color color) {
478            this.color = color;
479        }
480
481        @Override
482        public Insets getBorderInsets(Component c) {
483            return new Insets(0, 1, 1, 1);
484        }
485
486        @Override
487        public boolean isBorderOpaque() {
488            return true;
489        }
490
491        @Override
492        public void paintBorder(Component c, Graphics g, int x, int y,
493                int width, int height) {
494            g.setColor(color);
495            g.drawLine(x, y, x, y + height - 1);
496            g.drawLine(x, y + height - 1, x + width - 1, y + height - 1);
497            g.drawLine(x + width - 1, y, x + width - 1, y + height - 1);
498        }
499    }
500
501    /**
502     * The border of the taskpane group paints the "text", the "icon", the
503     * "expanded" status and the "special" type.
504     * 
505     */
506    protected class PaneBorder implements Border, UIResource {
507
508        protected Color borderColor;
509        protected Color titleForeground;
510        protected Color specialTitleBackground;
511        protected Color specialTitleForeground;
512        protected Color titleBackgroundGradientStart;
513        protected Color titleBackgroundGradientEnd;
514
515        protected Color titleOver;
516        protected Color specialTitleOver;
517
518        protected JLabel label;
519
520        /**
521         * Creates new instance of individual pane border.
522         */
523        public PaneBorder() {
524            borderColor = UIManager.getColor("TaskPane.borderColor");
525
526            titleForeground = UIManager.getColor("TaskPane.titleForeground");
527
528            specialTitleBackground = UIManager
529                    .getColor("TaskPane.specialTitleBackground");
530            specialTitleForeground = UIManager
531                    .getColor("TaskPane.specialTitleForeground");
532
533            titleBackgroundGradientStart = UIManager
534                    .getColor("TaskPane.titleBackgroundGradientStart");
535            titleBackgroundGradientEnd = UIManager
536                    .getColor("TaskPane.titleBackgroundGradientEnd");
537
538            titleOver = UIManager.getColor("TaskPane.titleOver");
539            if (titleOver == null) {
540                titleOver = specialTitleBackground.brighter();
541            }
542            specialTitleOver = UIManager.getColor("TaskPane.specialTitleOver");
543            if (specialTitleOver == null) {
544                specialTitleOver = specialTitleBackground.brighter();
545            }
546
547            label = new JLabel();
548            label.setOpaque(false);
549            label.setIconTextGap(8);
550        }
551
552        @Override
553        public Insets getBorderInsets(Component c) {
554            return new Insets(getTitleHeight(c), 0, 0, 0);
555        }
556
557        /**
558         * Overwritten to always return <code>true</code> to speed up
559         * painting. Don't use transparent borders unless providing UI delegate
560         * that provides proper return value when calling this method.
561         * 
562         * @see javax.swing.border.Border#isBorderOpaque()
563         */
564        @Override
565        public boolean isBorderOpaque() {
566            return true;
567        }
568
569        /**
570         * Calculates the preferred border size, its size so all its content
571         * fits.
572         * 
573         * @param group
574         *            Selected group.
575         */
576        public Dimension getPreferredSize(JXTaskPane group) {
577            // calculate the title width so it is fully visible
578            // it starts with the title width
579            configureLabel(group);
580            Dimension dim = label.getPreferredSize();
581            // add the title left offset
582            dim.width += 3;
583            // add the controls width
584            dim.width += getTitleHeight(group);
585            // and some space between label and controls
586            dim.width += 3;
587
588            dim.height = getTitleHeight(group);
589            return dim;
590        }
591
592        /**
593         * Paints background of the title. This may differ based on properties
594         * of the group.
595         * 
596         * @param group
597         *            Selected group.
598         * @param g
599         *            Target graphics.
600         */
601        protected void paintTitleBackground(JXTaskPane group, Graphics g) {
602            if (group.isSpecial()) {
603                g.setColor(specialTitleBackground);
604            } else {
605                g.setColor(titleBackgroundGradientStart);
606            }
607            g.fillRect(0, 0, group.getWidth(), getTitleHeight(group) - 1);
608        }
609
610        /**
611         * Paints current group title.
612         * 
613         * @param group
614         *            Selected group.
615         * @param g
616         *            Target graphics.
617         * @param textColor
618         *            Title color.
619         * @param x
620         *            X coordinate of the top left corner.
621         * @param y
622         *            Y coordinate of the top left corner.
623         * @param width
624         *            Width of the box.
625         * @param height
626         *            Height of the box.
627         */
628        protected void paintTitle(JXTaskPane group, Graphics g,
629                Color textColor, int x, int y, int width, int height) {
630            configureLabel(group);
631            label.setForeground(textColor);
632            if (group.getFont() != null && ! (group.getFont() instanceof FontUIResource)) {
633                label.setFont(group.getFont());
634            }
635            g.translate(x, y);
636            label.setBounds(0, 0, width, height);
637            label.paint(g);
638            g.translate(-x, -y);
639        }
640
641        /**
642         * Configures label for the group using its title, font, icon and
643         * orientation.
644         * 
645         * @param group
646         *            Selected group.
647         */
648        protected void configureLabel(JXTaskPane group) {
649            label.applyComponentOrientation(group.getComponentOrientation());
650            label.setFont(group.getFont());
651            label.setText(group.getTitle());
652            label.setIcon(group.getIcon() == null ? new EmptyIcon() : group
653                    .getIcon());
654        }
655
656        /**
657         * Paints expanded controls. Default implementation does nothing.
658         * 
659         * @param group
660         *            Expanded group.
661         * @param g
662         *            Target graphics.
663         * @param x
664         *            X coordinate of the top left corner.
665         * @param y
666         *            Y coordinate of the top left corner.
667         * @param width
668         *            Width of the box.
669         * @param height
670         *            Height of the box.
671         */
672        protected void paintExpandedControls(JXTaskPane group, Graphics g,
673                int x, int y, int width, int height) {
674        }
675
676        /**
677         * Gets current paint color.
678         * 
679         * @param group
680         *            Selected group.
681         * @return Color to be used for painting provided group.
682         */
683        protected Color getPaintColor(JXTaskPane group) {
684            Color paintColor;
685            if (isMouseOverBorder()) {
686                if (mouseOver) {
687                    if (group.isSpecial()) {
688                        paintColor = specialTitleOver;
689                    } else {
690                        paintColor = titleOver;
691                    }
692                } else {
693                    if (group.isSpecial()) {
694                        paintColor = specialTitleForeground;
695                    } else {
696                        paintColor = group.getForeground() == null || group.getForeground() instanceof ColorUIResource ? titleForeground : group.getForeground();
697                    }
698                }
699            } else {
700                if (group.isSpecial()) {
701                    paintColor = specialTitleForeground;
702                } else {
703                    paintColor = group.getForeground() == null || group.getForeground() instanceof ColorUIResource ? titleForeground : group.getForeground();
704                }
705            }
706            return paintColor;
707        }
708
709        /*
710         * @see javax.swing.border.Border#paintBorder(java.awt.Component,
711         *      java.awt.Graphics, int, int, int, int)
712         */
713        @Override
714        public void paintBorder(Component c, Graphics g, int x, int y,
715                int width, int height) {
716
717            JXTaskPane group = (JXTaskPane) c;
718
719            // calculate position of title and toggle controls
720            int controlWidth = getTitleHeight(group) - 2 * getRoundHeight();
721            int controlX = group.getWidth() - getTitleHeight(group);
722            int controlY = getRoundHeight() - 1;
723            int titleX = 3;
724            int titleY = 0;
725            int titleWidth = group.getWidth() - getTitleHeight(group) - 3;
726            int titleHeight = getTitleHeight(group);
727
728            if (!group.getComponentOrientation().isLeftToRight()) {
729                controlX = group.getWidth() - controlX - controlWidth;
730                titleX = group.getWidth() - titleX - titleWidth;
731            }
732
733            // paint the title background
734            paintTitleBackground(group, g);
735
736            // paint the the toggles
737            paintExpandedControls(group, g, controlX, controlY, controlWidth,
738                    controlWidth);
739
740            // paint the title text and icon
741            Color paintColor = getPaintColor(group);
742
743            // focus painted same color as text
744            if (group.hasFocus()) {
745                paintFocus(g, paintColor, 3, 3, width - 6, getTitleHeight(group) - 6);
746            }
747
748            paintTitle(group, g, paintColor, titleX, titleY, titleWidth,
749                    titleHeight);
750        }
751
752        /**
753         * Paints oval 'border' area around the control itself.
754         * 
755         * @param group
756         *            Expanded group.
757         * @param g
758         *            Target graphics.
759         * @param x
760         *            X coordinate of the top left corner.
761         * @param y
762         *            Y coordinate of the top left corner.
763         * @param width
764         *            Width of the box.
765         * @param height
766         *            Height of the box.
767         */
768        protected void paintRectAroundControls(JXTaskPane group, Graphics g,
769                int x, int y, int width, int height, Color highColor,
770                Color lowColor) {
771            if (mouseOver) {
772                int x2 = x + width;
773                int y2 = y + height;
774                g.setColor(highColor);
775                g.drawLine(x, y, x2, y);
776                g.drawLine(x, y, x, y2);
777                g.setColor(lowColor);
778                g.drawLine(x2, y, x2, y2);
779                g.drawLine(x, y2, x2, y2);
780            }
781        }
782
783        /**
784         * Paints oval 'border' area around the control itself.
785         * 
786         * @param group
787         *            Expanded group.
788         * @param g
789         *            Target graphics.
790         * @param x
791         *            X coordinate of the top left corner.
792         * @param y
793         *            Y coordinate of the top left corner.
794         * @param width
795         *            Width of the box.
796         * @param height
797         *            Height of the box.
798         */
799        protected void paintOvalAroundControls(JXTaskPane group, Graphics g,
800                int x, int y, int width, int height) {
801            if (group.isSpecial()) {
802                g.setColor(specialTitleBackground.brighter());
803                g.drawOval(x, y, width, height);
804            } else {
805                g.setColor(titleBackgroundGradientStart);
806                g.fillOval(x, y, width, height);
807
808                g.setColor(titleBackgroundGradientEnd.darker());
809                g.drawOval(x, y, width, width);
810            }
811        }
812
813        /**
814         * Paints controls for the group.
815         * 
816         * @param group
817         *            Expanded group.
818         * @param g
819         *            Target graphics.
820         * @param x
821         *            X coordinate of the top left corner.
822         * @param y
823         *            Y coordinate of the top left corner.
824         * @param width
825         *            Width of the box.
826         * @param height
827         *            Height of the box.
828         */
829        protected void paintChevronControls(JXTaskPane group, Graphics g,
830                int x, int y, int width, int height) {
831            ChevronIcon chevron;
832            if (group.isCollapsed()) {
833                chevron = new ChevronIcon(false);
834            } else {
835                chevron = new ChevronIcon(true);
836            }
837            int chevronX = x + width / 2 - chevron.getIconWidth() / 2;
838            int chevronY = y + (height / 2 - chevron.getIconHeight());
839            chevron.paintIcon(group, g, chevronX, chevronY);
840            chevron.paintIcon(group, g, chevronX, chevronY
841                    + chevron.getIconHeight() + 1);
842        }
843
844        /**
845         * Paints focused group.
846         * 
847         * @param g
848         *            Target graphics.
849         * @param paintColor
850         *            Focused group color.
851         * @param x
852         *            X coordinate of the top left corner.
853         * @param y
854         *            Y coordinate of the top left corner.
855         * @param width
856         *            Width of the box.
857         * @param height
858         *            Height of the box.
859         */
860        protected void paintFocus(Graphics g, Color paintColor, int x, int y,
861                int width, int height) {
862            g.setColor(paintColor);
863            BasicGraphicsUtils.drawDashedRect(g, x, y, width, height);
864        }
865
866        /**
867         * Default implementation returns false.
868         * 
869         * @return true if this border wants to display things differently when
870         *         the mouse is over it
871         */
872        protected boolean isMouseOverBorder() {
873            return false;
874        }
875    }
876
877    /**
878     * Gets size of arc used to round corners.
879     * 
880     * @return size of arc used to round corners of the panel.
881     */
882    protected int getRoundHeight() {
883        return roundHeight;
884    }
885
886}