001    /*
002     * $Id: BasicTaskPaneUI.java,v 1.7 2005/11/17 00:40:31 rbair Exp $
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.Graphics;
028    import java.awt.Insets;
029    import java.awt.Rectangle;
030    import java.awt.event.ActionEvent;
031    import java.awt.event.FocusEvent;
032    import java.awt.event.FocusListener;
033    import java.awt.event.MouseEvent;
034    import java.beans.PropertyChangeEvent;
035    import java.beans.PropertyChangeListener;
036    
037    import javax.swing.AbstractAction;
038    import javax.swing.Action;
039    import javax.swing.ActionMap;
040    import javax.swing.BorderFactory;
041    import javax.swing.Icon;
042    import javax.swing.InputMap;
043    import javax.swing.JComponent;
044    import javax.swing.JLabel;
045    import javax.swing.LookAndFeel;
046    import javax.swing.SwingUtilities;
047    import javax.swing.UIManager;
048    import javax.swing.border.Border;
049    import javax.swing.border.CompoundBorder;
050    import javax.swing.event.MouseInputAdapter;
051    import javax.swing.event.MouseInputListener;
052    import javax.swing.plaf.ActionMapUIResource;
053    import javax.swing.plaf.ComponentUI;
054    import javax.swing.plaf.basic.BasicGraphicsUtils;
055    
056    import org.jdesktop.swingx.JXCollapsiblePane;
057    import org.jdesktop.swingx.JXHyperlink;
058    import org.jdesktop.swingx.JXTaskPane;
059    import org.jdesktop.swingx.icon.EmptyIcon;
060    import org.jdesktop.swingx.plaf.TaskPaneUI;
061    
062    /**
063     * Base implementation of the <code>JXTaskPane</code> UI.
064     * 
065     * @author <a href="mailto:fred@L2FProd.com">Frederic Lavigne</a>
066     */
067    public class BasicTaskPaneUI extends TaskPaneUI {
068    
069      private static FocusListener focusListener = new RepaintOnFocus();
070    
071      public static ComponentUI createUI(JComponent c) {
072        return new BasicTaskPaneUI();
073      }
074    
075      protected static int TITLE_HEIGHT = 25;
076      protected static int ROUND_HEIGHT = 5;
077      
078      protected JXTaskPane group;
079    
080      protected boolean mouseOver;
081      protected MouseInputListener mouseListener;
082    
083      protected PropertyChangeListener propertyListener;
084      
085      public void installUI(JComponent c) {
086        super.installUI(c);
087        group = (JXTaskPane)c;
088    
089        installDefaults();
090        installListeners();
091        installKeyboardActions();
092      }
093    
094      protected void installDefaults() {
095        group.setOpaque(true);
096        group.setBorder(createPaneBorder());
097        ((JComponent)group.getContentPane()).setBorder(createContentPaneBorder());
098    
099        LookAndFeel.installColorsAndFont(
100          group,
101          "TaskPane.background",
102          "TaskPane.foreground",
103          "TaskPane.font");
104    
105        LookAndFeel.installColorsAndFont(
106          (JComponent)group.getContentPane(),
107          "TaskPane.background",
108          "TaskPane.foreground",
109          "TaskPane.font");    
110      }
111    
112      protected void installListeners() {
113        mouseListener = createMouseInputListener();
114        group.addMouseMotionListener(mouseListener);
115        group.addMouseListener(mouseListener);
116    
117        group.addFocusListener(focusListener);
118        propertyListener = createPropertyListener();
119        group.addPropertyChangeListener(propertyListener);
120      }
121    
122      protected void installKeyboardActions() {
123        InputMap inputMap = (InputMap)UIManager.get("TaskPane.focusInputMap");
124        if (inputMap != null) {
125          SwingUtilities.replaceUIInputMap(
126            group,
127            JComponent.WHEN_FOCUSED,
128            inputMap);
129        }
130    
131        ActionMap map = getActionMap();
132        if (map != null) {
133          SwingUtilities.replaceUIActionMap(group, map);
134        }
135      }
136    
137      ActionMap getActionMap() {
138        ActionMap map = new ActionMapUIResource();
139        map.put("toggleExpanded", new ToggleExpandedAction());
140        return map;
141      }
142    
143      public void uninstallUI(JComponent c) {
144        uninstallListeners();
145        super.uninstallUI(c);
146      }
147    
148      protected void uninstallListeners() {
149        group.removeMouseListener(mouseListener);
150        group.removeMouseMotionListener(mouseListener);
151        group.removeFocusListener(focusListener);
152        group.removePropertyChangeListener(propertyListener);
153      }
154    
155      protected MouseInputListener createMouseInputListener() {
156        return new ToggleListener();
157      }
158    
159      protected PropertyChangeListener createPropertyListener() {
160        return new ChangeListener();
161      }
162      
163      protected boolean isInBorder(MouseEvent event) {
164        return event.getY() < getTitleHeight();
165      }
166    
167      protected final int getTitleHeight() {
168        return TITLE_HEIGHT;
169      }
170    
171      protected Border createPaneBorder() {
172        return new PaneBorder();
173      }
174    
175      @Override
176      public Dimension getPreferredSize(JComponent c) {
177        Component component = group.getComponent(0);
178        if (!(component instanceof JXCollapsiblePane)) {
179          // something wrong in this JXTaskPane
180          return super.getPreferredSize(c);
181        }
182        
183        JXCollapsiblePane collapsible = (JXCollapsiblePane)component;
184        Dimension dim = collapsible.getPreferredSize();
185        
186        Border groupBorder = group.getBorder();
187        if (groupBorder instanceof PaneBorder) {
188          Dimension border = ((PaneBorder)groupBorder).getPreferredSize(group);
189          dim.width = Math.max(dim.width, border.width);
190          dim.height += border.height;
191        } else {
192          dim.height += getTitleHeight();
193        }      
194        
195        return dim;
196      }
197      
198      protected Border createContentPaneBorder() {
199        Color borderColor = UIManager.getColor("TaskPane.borderColor");
200        return new CompoundBorder(new ContentPaneBorder(borderColor), BorderFactory
201          .createEmptyBorder(10, 10, 10, 10));
202      }
203      
204      public Component createAction(Action action) {
205        JXHyperlink button = new JXHyperlink(action);
206        button.setOpaque(false);
207        button.setBorder(null);
208        button.setBorderPainted(false);
209        button.setFocusPainted(true);
210        button.setForeground(UIManager.getColor("TaskPane.titleForeground"));
211        return button;
212      }
213    
214      protected void ensureVisible() {
215        SwingUtilities.invokeLater(new Runnable() {
216          public void run() {
217            group.scrollRectToVisible(
218              new Rectangle(group.getWidth(), group.getHeight()));
219          }
220        });
221      }
222      
223      static class RepaintOnFocus implements FocusListener {
224        public void focusGained(FocusEvent e) {
225          e.getComponent().repaint();
226        }
227        public void focusLost(FocusEvent e) {
228          e.getComponent().repaint();
229        }
230      }
231      
232      class ChangeListener implements PropertyChangeListener {
233        public void propertyChange(PropertyChangeEvent evt) {
234          // if group is expanded but not animated
235          // or if animated has reached expanded state
236          // scroll to visible if scrollOnExpand is enabled
237          if ((JXTaskPane.EXPANDED_CHANGED_KEY.equals(evt.getPropertyName())
238            && Boolean.TRUE.equals(evt.getNewValue()) && !group.isAnimated())
239            || (JXCollapsiblePane.ANIMATION_STATE_KEY.equals(evt.getPropertyName()) && "expanded"
240              .equals(evt.getNewValue()))) {
241            if (group.isScrollOnExpand()) {
242              ensureVisible();
243            }
244          }
245        }
246      }
247      
248      class ToggleListener extends MouseInputAdapter {
249        public void mouseEntered(MouseEvent e) {
250          if (isInBorder(e)) {
251            e.getComponent().setCursor(
252              Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
253          } else {
254            mouseOver = false;
255            group.repaint();
256          }
257        }
258        public void mouseExited(MouseEvent e) {
259          e.getComponent().setCursor(Cursor.getDefaultCursor());
260          mouseOver = false;
261          group.repaint();
262        }
263        public void mouseMoved(MouseEvent e) {
264          if (isInBorder(e)) {
265            e.getComponent().setCursor(
266              Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
267            mouseOver = true;
268            group.repaint();
269          } else {
270            e.getComponent().setCursor(Cursor.getDefaultCursor());
271            mouseOver = false;
272            group.repaint();
273          }
274        }
275        public void mouseReleased(MouseEvent e) {
276          if (isInBorder(e)) {
277            group.setExpanded(!group.isExpanded());
278          }
279        }
280      }
281      
282      class ToggleExpandedAction extends AbstractAction {
283        public ToggleExpandedAction() {
284          super("toggleExpanded");
285        }
286        public void actionPerformed(ActionEvent e) {
287          group.setExpanded(!group.isExpanded());
288        }
289        public boolean isEnabled() {
290          return group.isVisible();
291        }
292      }
293    
294      protected static class ChevronIcon implements Icon {
295        boolean up = true;
296        public ChevronIcon(boolean up) {
297          this.up = up;
298        }
299        public int getIconHeight() {
300          return 3;
301        }
302        public int getIconWidth() {
303          return 6;
304        }
305        public void paintIcon(Component c, Graphics g, int x, int y) {
306          if (up) {
307            g.drawLine(x + 3, y, x, y + 3);
308            g.drawLine(x + 3, y, x + 6, y + 3);
309          } else {
310            g.drawLine(x, y, x + 3, y + 3);
311            g.drawLine(x + 3, y + 3, x + 6, y);
312          }
313        }
314      }
315    
316      /**
317       * The border around the content pane
318       */
319      protected static class ContentPaneBorder implements Border {
320        Color color;
321        public ContentPaneBorder(Color color) {
322          this.color = color;
323        }
324        public Insets getBorderInsets(Component c) {
325          return new Insets(0, 1, 1, 1);
326        }
327        public boolean isBorderOpaque() {
328          return true;
329        }
330        public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
331          g.setColor(color);
332          g.drawLine(x, y, x, y + height - 1);
333          g.drawLine(x, y + height - 1, x + width - 1, y + height - 1);
334          g.drawLine(x + width - 1, y, x + width - 1, y + height - 1);
335        }
336      }
337      
338      /**
339       * The border of the taskpane group paints the "text", the "icon", the
340       * "expanded" status and the "special" type.
341       *  
342       */
343      protected class PaneBorder implements Border {
344    
345        protected Color borderColor;
346        protected Color titleForeground;
347        protected Color specialTitleBackground;
348        protected Color specialTitleForeground;
349        protected Color titleBackgroundGradientStart;
350        protected Color titleBackgroundGradientEnd;
351    
352        protected Color titleOver;
353        protected Color specialTitleOver;
354        
355        protected JLabel label;
356        
357        public PaneBorder() {
358          borderColor = UIManager.getColor("TaskPane.borderColor");      
359    
360          titleForeground = UIManager.getColor("TaskPane.titleForeground");
361    
362          specialTitleBackground = UIManager
363            .getColor("TaskPane.specialTitleBackground");
364          specialTitleForeground = UIManager
365            .getColor("TaskPane.specialTitleForeground");
366    
367          titleBackgroundGradientStart = UIManager
368            .getColor("TaskPane.titleBackgroundGradientStart");
369          titleBackgroundGradientEnd = UIManager
370            .getColor("TaskPane.titleBackgroundGradientEnd");
371          
372          titleOver = UIManager.getColor("TaskPane.titleOver");
373          if (titleOver == null) {
374            titleOver = specialTitleBackground.brighter();
375          }
376          specialTitleOver = UIManager.getColor("TaskPane.specialTitleOver");
377          if (specialTitleOver == null) {
378            specialTitleOver = specialTitleBackground.brighter();
379          }
380          
381          label = new JLabel();
382          label.setOpaque(false);
383          label.setIconTextGap(8);
384        }
385        
386        public Insets getBorderInsets(Component c) {
387          return new Insets(getTitleHeight(), 0, 0, 0);
388        }
389    
390        public boolean isBorderOpaque() {
391          return true;
392        }
393    
394        /**
395         * Calculates the preferred border size, its size so all its content fits.
396         */
397        public Dimension getPreferredSize(JXTaskPane group) {
398          // calculate the title width so it is fully visible
399          // it starts with the title width
400          configureLabel(group);
401          Dimension dim = label.getPreferredSize();
402          // add the title left offset
403          dim.width += 3;
404          // add the controls width
405          dim.width += TITLE_HEIGHT;
406          // and some space between label and controls
407          dim.width += 3;
408          
409          dim.height = getTitleHeight();
410          return dim;
411        }
412        
413        protected void paintTitleBackground(JXTaskPane group, Graphics g) {
414          if (group.isSpecial()) {
415            g.setColor(specialTitleBackground);
416          } else {
417            g.setColor(titleBackgroundGradientStart);
418          }
419          g.fillRect(0, 0, group.getWidth(), getTitleHeight() - 1);
420        }
421    
422        protected void paintTitle(
423          JXTaskPane group,
424          Graphics g,
425          Color textColor,
426          int x,
427          int y,
428          int width,
429          int height) {
430          configureLabel(group);
431          label.setForeground(textColor);
432          g.translate(x, y);
433          label.setBounds(0, 0, width, height);
434          label.paint(g);
435          g.translate(-x, -y);
436        }
437    
438        protected void configureLabel(JXTaskPane group) {
439          label.applyComponentOrientation(group.getComponentOrientation());
440          label.setFont(group.getFont());
441          label.setText(group.getTitle());
442          label.setIcon(
443            group.getIcon() == null ? new EmptyIcon() : group.getIcon());      
444        }
445        
446        protected void paintExpandedControls(JXTaskPane group, Graphics g, int x,
447          int y, int width, int height) {}
448    
449        protected Color getPaintColor(JXTaskPane group) {
450          Color paintColor;
451          if (isMouseOverBorder()) {
452            if (mouseOver) {
453              if (group.isSpecial()) {
454                paintColor = specialTitleOver;
455              } else {
456                paintColor = titleOver;
457              }
458            } else {
459              if (group.isSpecial()) {
460                paintColor = specialTitleForeground;
461              } else {
462                paintColor = titleForeground;
463              }
464            }
465          } else {
466            if (group.isSpecial()) {
467              paintColor = specialTitleForeground;
468            } else {
469              paintColor = titleForeground;
470            }
471          }
472          return paintColor;
473        }
474        
475        public void paintBorder(
476          Component c,
477          Graphics g,
478          int x,
479          int y,
480          int width,
481          int height) {
482    
483          JXTaskPane group = (JXTaskPane)c;
484    
485          // calculate position of title and toggle controls
486          int controlWidth = TITLE_HEIGHT - 2 * ROUND_HEIGHT;
487          int controlX = group.getWidth() - TITLE_HEIGHT;
488          int controlY = ROUND_HEIGHT - 1;
489          int titleX = 3;
490          int titleY = 0;
491          int titleWidth = group.getWidth() - getTitleHeight() - 3;
492          int titleHeight = getTitleHeight();
493          
494          if (!group.getComponentOrientation().isLeftToRight()) {
495            controlX = group.getWidth() - controlX - controlWidth;        
496            titleX = group.getWidth() - titleX - titleWidth;
497          }
498          
499          // paint the title background
500          paintTitleBackground(group, g);
501    
502          // paint the the toggles
503          paintExpandedControls(group, g, controlX, controlY, controlWidth,
504            controlWidth);
505    
506          // paint the title text and icon
507          Color paintColor = getPaintColor(group);
508    
509          // focus painted same color as text
510          if (group.hasFocus()) {
511            paintFocus(g, 
512              paintColor, 
513              3,
514              3,
515              width - 6,
516              getTitleHeight() - 6);
517          }
518    
519          paintTitle(
520            group,
521            g,
522            paintColor,
523            titleX,
524            titleY,
525            titleWidth,
526            titleHeight);
527        }
528        
529        protected void paintRectAroundControls(JXTaskPane group, Graphics g, int x,
530          int y, int width, int height, Color highColor, Color lowColor) {      
531          if (mouseOver) {
532            int x2 = x + width;
533            int y2 = y + height;
534            g.setColor(highColor);
535            g.drawLine(x, y, x2, y);
536            g.drawLine(x, y, x, y2);
537            g.setColor(lowColor);
538            g.drawLine(x2, y, x2, y2);
539            g.drawLine(x, y2, x2, y2);
540          }
541        }
542        
543        protected void paintOvalAroundControls(JXTaskPane group, Graphics g, int x,
544          int y, int width, int height) {      
545          if (group.isSpecial()) {
546            g.setColor(specialTitleBackground.brighter());
547            g.drawOval(
548              x,
549              y,
550              width,
551              height);
552          } else {
553            g.setColor(titleBackgroundGradientStart);
554            g.fillOval(
555              x,
556              y,
557              width,
558              height);
559    
560            g.setColor(titleBackgroundGradientEnd.darker());
561            g.drawOval(
562              x,
563              y,
564              width,
565              width);
566          }
567        }
568        
569        protected void paintChevronControls(JXTaskPane group, Graphics g, int x,
570          int y, int width, int height) {      
571          ChevronIcon chevron;
572          if (group.isExpanded()) {
573            chevron = new ChevronIcon(true);
574          } else {
575            chevron = new ChevronIcon(false);
576          }
577          int chevronX = x + width / 2 - chevron.getIconWidth() / 2;
578          int chevronY = y + (height / 2 - chevron.getIconHeight());
579          chevron.paintIcon(group, g, chevronX, chevronY);
580          chevron.paintIcon(
581            group,
582            g,
583            chevronX,
584            chevronY + chevron.getIconHeight() + 1);
585        }
586        
587        protected void paintFocus(Graphics g, Color paintColor, int x, int y, int width, int height) {
588            g.setColor(paintColor);
589            BasicGraphicsUtils.drawDashedRect(
590              g,
591              x,
592              y,
593              width,
594              height);
595        }
596        
597        /**
598         * Default implementation returns false.
599         *  
600         * @return true if this border wants to display things differently when the
601         *         mouse is over it
602         */
603        protected boolean isMouseOverBorder() {
604          return false;
605        }    
606      }
607    
608    }