001    /*
002     * $Id: BasicTaskPaneUI.java 3366 2009-06-17 02:28:19Z 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     */
021    package org.jdesktop.swingx.plaf.basic;
022    
023    import java.awt.Color;
024    import java.awt.Component;
025    import java.awt.Cursor;
026    import java.awt.Dimension;
027    import java.awt.Font;
028    import java.awt.Graphics;
029    import java.awt.Insets;
030    import java.awt.Rectangle;
031    import java.awt.event.ActionEvent;
032    import java.awt.event.FocusEvent;
033    import java.awt.event.FocusListener;
034    import java.awt.event.MouseEvent;
035    import java.beans.PropertyChangeEvent;
036    import java.beans.PropertyChangeListener;
037    
038    import javax.swing.AbstractAction;
039    import javax.swing.Action;
040    import javax.swing.ActionMap;
041    import javax.swing.BorderFactory;
042    import javax.swing.Icon;
043    import javax.swing.InputMap;
044    import javax.swing.JComponent;
045    import javax.swing.JLabel;
046    import javax.swing.LookAndFeel;
047    import javax.swing.SwingUtilities;
048    import javax.swing.UIManager;
049    import javax.swing.border.Border;
050    import javax.swing.border.CompoundBorder;
051    import javax.swing.event.MouseInputAdapter;
052    import javax.swing.event.MouseInputListener;
053    import javax.swing.plaf.ActionMapUIResource;
054    import javax.swing.plaf.ColorUIResource;
055    import javax.swing.plaf.ComponentUI;
056    import javax.swing.plaf.FontUIResource;
057    import javax.swing.plaf.UIResource;
058    import javax.swing.plaf.basic.BasicGraphicsUtils;
059    
060    import org.jdesktop.swingx.JXCollapsiblePane;
061    import org.jdesktop.swingx.JXHyperlink;
062    import org.jdesktop.swingx.JXTaskPane;
063    import org.jdesktop.swingx.SwingXUtilities;
064    import org.jdesktop.swingx.icon.EmptyIcon;
065    import 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     */
072    public 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                public void run() {
313                    group.scrollRectToVisible(new Rectangle(group.getWidth(), group
314                            .getHeight()));
315                }
316            });
317        }
318    
319        /**
320         * Focus listener responsible for repainting of the taskpane on focus change.
321         */
322        static class RepaintOnFocus implements FocusListener {
323            public void focusGained(FocusEvent e) {
324                e.getComponent().repaint();
325            }
326    
327            public void focusLost(FocusEvent e) {
328                e.getComponent().repaint();
329            }
330        }
331    
332        /**
333         * Change listener responsible for change handling.
334         */
335        class ChangeListener implements PropertyChangeListener {
336            public void propertyChange(PropertyChangeEvent evt) {
337                // if group is expanded but not animated
338                // or if animated has reached expanded state
339                // scroll to visible if scrollOnExpand is enabled
340                if (("collapsed".equals(evt.getPropertyName())
341                        && Boolean.TRUE.equals(evt.getNewValue()) && !group
342                        .isAnimated())
343                        || (JXCollapsiblePane.ANIMATION_STATE_KEY.equals(evt
344                                .getPropertyName()) && "expanded".equals(evt
345                                .getNewValue()))) {
346                    if (group.isScrollOnExpand()) {
347                        ensureVisible();
348                    }
349                } else if (JXTaskPane.ICON_CHANGED_KEY
350                        .equals(evt.getPropertyName())
351                        || JXTaskPane.TITLE_CHANGED_KEY.equals(evt
352                                .getPropertyName())
353                        || JXTaskPane.SPECIAL_CHANGED_KEY.equals(evt
354                                .getPropertyName())) {
355                    // icon, title, special must lead to a repaint()
356                    group.repaint();
357                } else if ("mnemonic".equals(evt.getPropertyName())) {
358                    SwingXUtilities.updateMnemonicBinding(group, "toggleCollapsed");
359                    
360                    Border b = group.getBorder();
361                    
362                    if (b instanceof PaneBorder) {
363                        int key = (Integer) evt.getNewValue();
364                        ((PaneBorder) b).label.setDisplayedMnemonic(key);
365                    }
366                }
367            }
368        }
369    
370        /**
371         * Mouse listener responsible for handling of toggle events.
372         */
373        class ToggleListener extends MouseInputAdapter {
374            public void mouseEntered(MouseEvent e) {
375                if (isInBorder(e)) {
376                    e.getComponent().setCursor(
377                            Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
378                } else {
379                    mouseOver = false;
380                                    group.repaint(0, 0, group.getWidth(), getTitleHeight(group));
381                }
382            }
383    
384            public void mouseExited(MouseEvent e) {
385                e.getComponent().setCursor(null);
386                mouseOver = false;
387                            group.repaint(0, 0, group.getWidth(), getTitleHeight(group));
388            }
389    
390            public void mouseMoved(MouseEvent e) {
391                if (isInBorder(e)) {
392                    e.getComponent().setCursor(
393                            Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
394                    mouseOver = true;
395                } else {
396                    e.getComponent().setCursor(null);
397                    mouseOver = false;
398                }
399                
400                            group.repaint(0, 0, group.getWidth(), getTitleHeight(group));
401            }
402    
403            public void mouseReleased(MouseEvent e) {
404                if (SwingUtilities.isLeftMouseButton(e) && isInBorder(e)) {
405                    group.setCollapsed(!group.isCollapsed());
406                }
407            }
408        }
409        
410        /**
411         * Toggle expanded action.
412         */
413        class ToggleCollapsedAction extends AbstractAction {
414            /**
415             * Serial version UID.
416             */
417            private static final long serialVersionUID = 5676859881615358815L;
418            
419            public ToggleCollapsedAction() {
420                super("toggleCollapsed");
421            }
422            
423            public void actionPerformed(ActionEvent e) {
424                group.setCollapsed(!group.isCollapsed());
425            }
426            
427            public boolean isEnabled() {
428                return group.isVisible();
429            }
430        }
431    
432        /**
433         * Toggle icon.
434         */
435        protected static class ChevronIcon implements Icon {
436            boolean up = true;
437    
438            public ChevronIcon(boolean up) {
439                this.up = up;
440            }
441    
442            public int getIconHeight() {
443                return 3;
444            }
445    
446            public int getIconWidth() {
447                return 6;
448            }
449    
450            public void paintIcon(Component c, Graphics g, int x, int y) {
451                if (up) {
452                    g.drawLine(x + 3, y, x, y + 3);
453                    g.drawLine(x + 3, y, x + 6, y + 3);
454                } else {
455                    g.drawLine(x, y, x + 3, y + 3);
456                    g.drawLine(x + 3, y + 3, x + 6, y);
457                }
458            }
459        }
460    
461        /**
462         * The border around the content pane
463         */
464        protected static class ContentPaneBorder implements Border, UIResource {
465            Color color;
466    
467            public ContentPaneBorder(Color color) {
468                this.color = color;
469            }
470    
471            public Insets getBorderInsets(Component c) {
472                return new Insets(0, 1, 1, 1);
473            }
474    
475            public boolean isBorderOpaque() {
476                return true;
477            }
478    
479            public void paintBorder(Component c, Graphics g, int x, int y,
480                    int width, int height) {
481                g.setColor(color);
482                g.drawLine(x, y, x, y + height - 1);
483                g.drawLine(x, y + height - 1, x + width - 1, y + height - 1);
484                g.drawLine(x + width - 1, y, x + width - 1, y + height - 1);
485            }
486        }
487    
488        /**
489         * The border of the taskpane group paints the "text", the "icon", the
490         * "expanded" status and the "special" type.
491         * 
492         */
493        protected class PaneBorder implements Border, UIResource {
494    
495            protected Color borderColor;
496            protected Color titleForeground;
497            protected Color specialTitleBackground;
498            protected Color specialTitleForeground;
499            protected Color titleBackgroundGradientStart;
500            protected Color titleBackgroundGradientEnd;
501    
502            protected Color titleOver;
503            protected Color specialTitleOver;
504    
505            protected JLabel label;
506    
507            /**
508             * Creates new instance of individual pane border.
509             */
510            public PaneBorder() {
511                borderColor = UIManager.getColor("TaskPane.borderColor");
512    
513                titleForeground = UIManager.getColor("TaskPane.titleForeground");
514    
515                specialTitleBackground = UIManager
516                        .getColor("TaskPane.specialTitleBackground");
517                specialTitleForeground = UIManager
518                        .getColor("TaskPane.specialTitleForeground");
519    
520                titleBackgroundGradientStart = UIManager
521                        .getColor("TaskPane.titleBackgroundGradientStart");
522                titleBackgroundGradientEnd = UIManager
523                        .getColor("TaskPane.titleBackgroundGradientEnd");
524    
525                titleOver = UIManager.getColor("TaskPane.titleOver");
526                if (titleOver == null) {
527                    titleOver = specialTitleBackground.brighter();
528                }
529                specialTitleOver = UIManager.getColor("TaskPane.specialTitleOver");
530                if (specialTitleOver == null) {
531                    specialTitleOver = specialTitleBackground.brighter();
532                }
533    
534                label = new JLabel();
535                label.setOpaque(false);
536                label.setIconTextGap(8);
537            }
538    
539            public Insets getBorderInsets(Component c) {
540                return new Insets(getTitleHeight(c), 0, 0, 0);
541            }
542    
543            /**
544             * Overwritten to always return <code>true</code> to speed up
545             * painting. Don't use transparent borders unless providing UI delegate
546             * that provides proper return value when calling this method.
547             * 
548             * @see javax.swing.border.Border#isBorderOpaque()
549             */
550            public boolean isBorderOpaque() {
551                return true;
552            }
553    
554            /**
555             * Calculates the preferred border size, its size so all its content
556             * fits.
557             * 
558             * @param group
559             *            Selected group.
560             */
561            public Dimension getPreferredSize(JXTaskPane group) {
562                // calculate the title width so it is fully visible
563                // it starts with the title width
564                configureLabel(group);
565                Dimension dim = label.getPreferredSize();
566                // add the title left offset
567                dim.width += 3;
568                // add the controls width
569                dim.width += getTitleHeight(group);
570                // and some space between label and controls
571                dim.width += 3;
572    
573                dim.height = getTitleHeight(group);
574                return dim;
575            }
576    
577            /**
578             * Paints background of the title. This may differ based on properties
579             * of the group.
580             * 
581             * @param group
582             *            Selected group.
583             * @param g
584             *            Target graphics.
585             */
586            protected void paintTitleBackground(JXTaskPane group, Graphics g) {
587                if (group.isSpecial()) {
588                    g.setColor(specialTitleBackground);
589                } else {
590                    g.setColor(titleBackgroundGradientStart);
591                }
592                g.fillRect(0, 0, group.getWidth(), getTitleHeight(group) - 1);
593            }
594    
595            /**
596             * Paints current group title.
597             * 
598             * @param group
599             *            Selected group.
600             * @param g
601             *            Target graphics.
602             * @param textColor
603             *            Title color.
604             * @param x
605             *            X coordinate of the top left corner.
606             * @param y
607             *            Y coordinate of the top left corner.
608             * @param width
609             *            Width of the box.
610             * @param height
611             *            Height of the box.
612             */
613            protected void paintTitle(JXTaskPane group, Graphics g,
614                    Color textColor, int x, int y, int width, int height) {
615                configureLabel(group);
616                label.setForeground(textColor);
617                if (group.getFont() != null && ! (group.getFont() instanceof FontUIResource)) {
618                    label.setFont(group.getFont());
619                }
620                g.translate(x, y);
621                label.setBounds(0, 0, width, height);
622                label.paint(g);
623                g.translate(-x, -y);
624            }
625    
626            /**
627             * Configures label for the group using its title, font, icon and
628             * orientation.
629             * 
630             * @param group
631             *            Selected group.
632             */
633            protected void configureLabel(JXTaskPane group) {
634                label.applyComponentOrientation(group.getComponentOrientation());
635                label.setFont(group.getFont());
636                label.setText(group.getTitle());
637                label.setIcon(group.getIcon() == null ? new EmptyIcon() : group
638                        .getIcon());
639            }
640    
641            /**
642             * Paints expanded controls. Default implementation does nothing.
643             * 
644             * @param group
645             *            Expanded group.
646             * @param g
647             *            Target graphics.
648             * @param x
649             *            X coordinate of the top left corner.
650             * @param y
651             *            Y coordinate of the top left corner.
652             * @param width
653             *            Width of the box.
654             * @param height
655             *            Height of the box.
656             */
657            protected void paintExpandedControls(JXTaskPane group, Graphics g,
658                    int x, int y, int width, int height) {
659            }
660    
661            /**
662             * Gets current paint color.
663             * 
664             * @param group
665             *            Selected group.
666             * @return Color to be used for painting provided group.
667             */
668            protected Color getPaintColor(JXTaskPane group) {
669                Color paintColor;
670                if (isMouseOverBorder()) {
671                    if (mouseOver) {
672                        if (group.isSpecial()) {
673                            paintColor = specialTitleOver;
674                        } else {
675                            paintColor = titleOver;
676                        }
677                    } else {
678                        if (group.isSpecial()) {
679                            paintColor = specialTitleForeground;
680                        } else {
681                            paintColor = group.getForeground() == null || group.getForeground() instanceof ColorUIResource ? titleForeground : group.getForeground();
682                        }
683                    }
684                } else {
685                    if (group.isSpecial()) {
686                        paintColor = specialTitleForeground;
687                    } else {
688                        paintColor = group.getForeground() == null || group.getForeground() instanceof ColorUIResource ? titleForeground : group.getForeground();
689                    }
690                }
691                return paintColor;
692            }
693    
694            /*
695             * @see javax.swing.border.Border#paintBorder(java.awt.Component,
696             *      java.awt.Graphics, int, int, int, int)
697             */
698            public void paintBorder(Component c, Graphics g, int x, int y,
699                    int width, int height) {
700    
701                JXTaskPane group = (JXTaskPane) c;
702    
703                // calculate position of title and toggle controls
704                int controlWidth = getTitleHeight(group) - 2 * getRoundHeight();
705                int controlX = group.getWidth() - getTitleHeight(group);
706                int controlY = getRoundHeight() - 1;
707                int titleX = 3;
708                int titleY = 0;
709                int titleWidth = group.getWidth() - getTitleHeight(group) - 3;
710                int titleHeight = getTitleHeight(group);
711    
712                if (!group.getComponentOrientation().isLeftToRight()) {
713                    controlX = group.getWidth() - controlX - controlWidth;
714                    titleX = group.getWidth() - titleX - titleWidth;
715                }
716    
717                // paint the title background
718                paintTitleBackground(group, g);
719    
720                // paint the the toggles
721                paintExpandedControls(group, g, controlX, controlY, controlWidth,
722                        controlWidth);
723    
724                // paint the title text and icon
725                Color paintColor = getPaintColor(group);
726    
727                // focus painted same color as text
728                if (group.hasFocus()) {
729                    paintFocus(g, paintColor, 3, 3, width - 6, getTitleHeight(group) - 6);
730                }
731    
732                paintTitle(group, g, paintColor, titleX, titleY, titleWidth,
733                        titleHeight);
734            }
735    
736            /**
737             * Paints oval 'border' area around the control itself.
738             * 
739             * @param group
740             *            Expanded group.
741             * @param g
742             *            Target graphics.
743             * @param x
744             *            X coordinate of the top left corner.
745             * @param y
746             *            Y coordinate of the top left corner.
747             * @param width
748             *            Width of the box.
749             * @param height
750             *            Height of the box.
751             */
752            protected void paintRectAroundControls(JXTaskPane group, Graphics g,
753                    int x, int y, int width, int height, Color highColor,
754                    Color lowColor) {
755                if (mouseOver) {
756                    int x2 = x + width;
757                    int y2 = y + height;
758                    g.setColor(highColor);
759                    g.drawLine(x, y, x2, y);
760                    g.drawLine(x, y, x, y2);
761                    g.setColor(lowColor);
762                    g.drawLine(x2, y, x2, y2);
763                    g.drawLine(x, y2, x2, y2);
764                }
765            }
766    
767            /**
768             * Paints oval 'border' area around the control itself.
769             * 
770             * @param group
771             *            Expanded group.
772             * @param g
773             *            Target graphics.
774             * @param x
775             *            X coordinate of the top left corner.
776             * @param y
777             *            Y coordinate of the top left corner.
778             * @param width
779             *            Width of the box.
780             * @param height
781             *            Height of the box.
782             */
783            protected void paintOvalAroundControls(JXTaskPane group, Graphics g,
784                    int x, int y, int width, int height) {
785                if (group.isSpecial()) {
786                    g.setColor(specialTitleBackground.brighter());
787                    g.drawOval(x, y, width, height);
788                } else {
789                    g.setColor(titleBackgroundGradientStart);
790                    g.fillOval(x, y, width, height);
791    
792                    g.setColor(titleBackgroundGradientEnd.darker());
793                    g.drawOval(x, y, width, width);
794                }
795            }
796    
797            /**
798             * Paints controls for the group.
799             * 
800             * @param group
801             *            Expanded group.
802             * @param g
803             *            Target graphics.
804             * @param x
805             *            X coordinate of the top left corner.
806             * @param y
807             *            Y coordinate of the top left corner.
808             * @param width
809             *            Width of the box.
810             * @param height
811             *            Height of the box.
812             */
813            protected void paintChevronControls(JXTaskPane group, Graphics g,
814                    int x, int y, int width, int height) {
815                ChevronIcon chevron;
816                if (group.isCollapsed()) {
817                    chevron = new ChevronIcon(false);
818                } else {
819                    chevron = new ChevronIcon(true);
820                }
821                int chevronX = x + width / 2 - chevron.getIconWidth() / 2;
822                int chevronY = y + (height / 2 - chevron.getIconHeight());
823                chevron.paintIcon(group, g, chevronX, chevronY);
824                chevron.paintIcon(group, g, chevronX, chevronY
825                        + chevron.getIconHeight() + 1);
826            }
827    
828            /**
829             * Paints focused group.
830             * 
831             * @param g
832             *            Target graphics.
833             * @param paintColor
834             *            Focused group color.
835             * @param x
836             *            X coordinate of the top left corner.
837             * @param y
838             *            Y coordinate of the top left corner.
839             * @param width
840             *            Width of the box.
841             * @param height
842             *            Height of the box.
843             */
844            protected void paintFocus(Graphics g, Color paintColor, int x, int y,
845                    int width, int height) {
846                g.setColor(paintColor);
847                BasicGraphicsUtils.drawDashedRect(g, x, y, width, height);
848            }
849    
850            /**
851             * Default implementation returns false.
852             * 
853             * @return true if this border wants to display things differently when
854             *         the mouse is over it
855             */
856            protected boolean isMouseOverBorder() {
857                return false;
858            }
859        }
860    
861        /**
862         * Gets size of arc used to round corners.
863         * 
864         * @return size of arc used to round corners of the panel.
865         */
866        protected int getRoundHeight() {
867            return roundHeight;
868        }
869    
870    }