001    /* BasicArrowButton.java --
002       Copyright (C) 2004, 2005, 2006 Free Software Foundation, Inc.
003    
004    This file is part of GNU Classpath.
005    
006    GNU Classpath is free software; you can redistribute it and/or modify
007    it under the terms of the GNU General Public License as published by
008    the Free Software Foundation; either version 2, or (at your option)
009    any later version.
010    
011    GNU Classpath is distributed in the hope that it will be useful, but
012    WITHOUT ANY WARRANTY; without even the implied warranty of
013    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014    General Public License for more details.
015    
016    You should have received a copy of the GNU General Public License
017    along with GNU Classpath; see the file COPYING.  If not, write to the
018    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019    02110-1301 USA.
020    
021    Linking this library statically or dynamically with other modules is
022    making a combined work based on this library.  Thus, the terms and
023    conditions of the GNU General Public License cover the whole
024    combination.
025    
026    As a special exception, the copyright holders of this library give you
027    permission to link this library with independent modules to produce an
028    executable, regardless of the license terms of these independent
029    modules, and to copy and distribute the resulting executable under
030    terms of your choice, provided that you also meet, for each linked
031    independent module, the terms and conditions of the license of that
032    module.  An independent module is a module which is not derived from
033    or based on this library.  If you modify this library, you may extend
034    this exception to your version of the library, but you are not
035    obligated to do so.  If you do not wish to do so, delete this
036    exception statement from your version. */
037    
038    
039    package javax.swing.plaf.basic;
040    
041    import java.awt.Color;
042    import java.awt.Dimension;
043    import java.awt.Graphics;
044    import java.awt.Polygon;
045    
046    import javax.swing.ButtonModel;
047    import javax.swing.JButton;
048    import javax.swing.SwingConstants;
049    
050    /**
051     * A button that displays an arrow (triangle) that points {@link #NORTH},
052     * {@link #SOUTH}, {@link #EAST} or {@link #WEST}.  This button is used by
053     * the {@link BasicComboBoxUI} class.
054     *
055     * @see BasicComboBoxUI#createArrowButton
056     */
057    public class BasicArrowButton extends JButton implements SwingConstants
058    {
059    
060      /**
061       * The direction that the arrow points.
062       *
063       * @see #getDirection()
064       */
065      protected int direction;
066    
067      /**
068       * The color the arrow is painted in if disabled and the bottom and right
069       * edges of the button.
070       * This is package-private to avoid an accessor method.
071       */
072      transient Color shadow = Color.GRAY;
073    
074      /**
075       * The color the arrow is painted in if enabled and the bottom and right
076       * edges of the button.
077       * This is package-private to avoid an accessor method.
078       */
079      transient Color darkShadow = new Color(102, 102, 102);
080    
081      /**
082       * The top and left edges of the button.
083       * This is package-private to avoid an accessor method.
084       */
085      transient Color highlight = Color.WHITE;
086    
087      /**
088       * Creates a new <code>BasicArrowButton</code> object with an arrow pointing
089       * in the specified direction.  If the <code>direction</code> is not one of
090       * the specified constants, no arrow is drawn.
091       *
092       * @param direction The direction the arrow points in (one of:
093       * {@link #NORTH}, {@link #SOUTH}, {@link #EAST} and {@link #WEST}).
094       */
095      public BasicArrowButton(int direction)
096      {
097        super();
098        setDirection(direction);
099        setFocusable(false);
100      }
101    
102      /**
103       * Creates a new BasicArrowButton object with the given colors and
104       * direction.
105       *
106       * @param direction The direction to point in (one of:
107       * {@link #NORTH}, {@link #SOUTH}, {@link #EAST} and {@link #WEST}).
108       * @param background The background color.
109       * @param shadow The shadow color.
110       * @param darkShadow The dark shadow color.
111       * @param highlight The highlight color.
112       */
113      public BasicArrowButton(int direction, Color background, Color shadow,
114                              Color darkShadow, Color highlight)
115      {
116        this(direction);
117        setBackground(background);
118        this.shadow = shadow;
119        this.darkShadow = darkShadow;
120        this.highlight = highlight;
121        setFocusable(false);
122      }
123    
124      /**
125       * Returns whether the focus can traverse to this component.  This method
126       * always returns <code>false</code>.
127       *
128       * @return <code>false</code>.
129       */
130      public boolean isFocusTraversable()
131      {
132        return false;
133      }
134    
135      /**
136       * Returns the direction of the arrow (one of: {@link #NORTH},
137       * {@link #SOUTH}, {@link #EAST} and {@link #WEST}).
138       *
139       * @return The direction of the arrow.
140       */
141      public int getDirection()
142      {
143        return direction;
144      }
145    
146      /**
147       * Sets the direction of the arrow.
148       *
149       * @param dir The new direction of the arrow (one of: {@link #NORTH},
150       *            {@link #SOUTH}, {@link #EAST} and {@link #WEST}).
151       */
152      public void setDirection(int dir)
153      {
154        this.direction = dir;
155      }
156    
157      /**
158       * Paints the arrow button. The painting is delegated to the
159       * paintTriangle method.
160       *
161       * @param g The Graphics object to paint with.
162       */
163      public void paint(Graphics g)
164      {
165        super.paint(g);
166    
167        int height = getHeight();
168        int size = height / 4;
169    
170        int x = (getWidth() - size) / 2;
171        int y = (height - size) / 2;
172    
173        ButtonModel m = getModel();
174        if (m.isArmed())
175          {
176            x++;
177            y++;
178          }
179    
180        paintTriangle(g, x, y, size, direction, isEnabled());
181      }
182    
183      /**
184       * Returns the preferred size of the arrow button.
185       *
186       * @return The preferred size (always 16 x 16).
187       */
188      public Dimension getPreferredSize()
189      {
190        // since Dimension is NOT immutable, we must return a new instance
191        // every time (if we return a cached value, the caller might modify it)
192        // - tests show that the reference implementation does the same.
193        return new Dimension(16, 16);
194      }
195    
196      /**
197       * Returns the minimum size of the arrow button.
198       *
199       * @return The minimum size (always 5 x 5).
200       */
201      public Dimension getMinimumSize()
202      {
203        // since Dimension is NOT immutable, we must return a new instance
204        // every time (if we return a cached value, the caller might modify it)
205        // - tests show that the reference implementation does the same.
206        return new Dimension(5, 5);
207      }
208    
209      /**
210       * Returns the maximum size of the arrow button.
211       *
212       * @return The maximum size (always Integer.MAX_VALUE x Integer.MAX_VALUE).
213       */
214      public Dimension getMaximumSize()
215      {
216        // since Dimension is NOT immutable, we must return a new instance
217        // every time (if we return a cached value, the caller might modify it)
218        // - tests show that the reference implementation does the same.
219        return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);
220      }
221    
222      /**
223       * Paints a triangle with the given size, location and direction.  It is
224       * difficult to explain the rationale behind the positioning of the triangle
225       * relative to the given (x, y) position - by trial and error we seem to
226       * match the behaviour of the reference implementation (which is missing a
227       * specification for this method).
228       *
229       * @param g  the graphics device.
230       * @param x  the x-coordinate for the triangle's location.
231       * @param y  the y-coordinate for the triangle's location.
232       * @param size  the arrow size (depth).
233       * @param direction  the direction of the arrow (one of: {@link #NORTH},
234       *            {@link #SOUTH}, {@link #EAST} and {@link #WEST}).
235       * @param isEnabled  if <code>true</code> the arrow is drawn in the enabled
236       *                   state, otherwise it is drawn in the disabled state.
237       */
238      public void paintTriangle(Graphics g, int x, int y, int size, int direction,
239                                boolean isEnabled)
240      {
241        Color savedColor = g.getColor();
242        switch (direction)
243          {
244          case NORTH:
245            paintTriangleNorth(g, x, y, size, isEnabled);
246            break;
247          case SOUTH:
248            paintTriangleSouth(g, x, y, size, isEnabled);
249            break;
250          case LEFT:
251          case WEST:
252            paintTriangleWest(g, x, y, size, isEnabled);
253            break;
254          case RIGHT:
255          case EAST:
256            paintTriangleEast(g, x, y, size, isEnabled);
257            break;
258          }
259        g.setColor(savedColor);
260      }
261    
262      /**
263       * Paints an upward-pointing triangle.  This method is called by the
264       * {@link #paintTriangle(Graphics, int, int, int, int, boolean)} method.
265       *
266       * @param g  the graphics device.
267       * @param x  the x-coordinate for the anchor point.
268       * @param y  the y-coordinate for the anchor point.
269       * @param size  the arrow size (depth).
270       * @param isEnabled  if <code>true</code> the arrow is drawn in the enabled
271       *                   state, otherwise it is drawn in the disabled state.
272       */
273      private void paintTriangleNorth(Graphics g, int x, int y, int size,
274              boolean isEnabled)
275      {
276        int tipX = x + (size - 2) / 2;
277        int tipY = y;
278        int baseX1 = tipX - (size - 1);
279        int baseX2 = tipX + (size - 1);
280        int baseY = y + (size - 1);
281        Polygon triangle = new Polygon();
282        triangle.addPoint(tipX, tipY);
283        triangle.addPoint(baseX1, baseY);
284        triangle.addPoint(baseX2, baseY);
285        if (isEnabled)
286         {
287           g.setColor(Color.DARK_GRAY);
288           g.fillPolygon(triangle);
289           g.drawPolygon(triangle);
290         }
291        else
292         {
293           g.setColor(Color.GRAY);
294           g.fillPolygon(triangle);
295           g.drawPolygon(triangle);
296           g.setColor(Color.WHITE);
297           g.drawLine(baseX1 + 1, baseY + 1, baseX2 + 1, baseY + 1);
298         }
299      }
300    
301      /**
302       * Paints an downward-pointing triangle.  This method is called by the
303       * {@link #paintTriangle(Graphics, int, int, int, int, boolean)} method.
304       *
305       * @param g  the graphics device.
306       * @param x  the x-coordinate for the anchor point.
307       * @param y  the y-coordinate for the anchor point.
308       * @param size  the arrow size (depth).
309       * @param isEnabled  if <code>true</code> the arrow is drawn in the enabled
310       *                   state, otherwise it is drawn in the disabled state.
311       */
312      private void paintTriangleSouth(Graphics g, int x, int y, int size,
313              boolean isEnabled)
314      {
315        int tipX = x + (size - 2) / 2;
316        int tipY = y + (size - 1);
317        int baseX1 = tipX - (size - 1);
318        int baseX2 = tipX + (size - 1);
319        int baseY = y;
320        Polygon triangle = new Polygon();
321        triangle.addPoint(tipX, tipY);
322        triangle.addPoint(baseX1, baseY);
323        triangle.addPoint(baseX2, baseY);
324        if (isEnabled)
325         {
326           g.setColor(Color.DARK_GRAY);
327           g.fillPolygon(triangle);
328           g.drawPolygon(triangle);
329         }
330        else
331         {
332           g.setColor(Color.GRAY);
333           g.fillPolygon(triangle);
334           g.drawPolygon(triangle);
335           g.setColor(Color.WHITE);
336           g.drawLine(tipX + 1, tipY, baseX2, baseY + 1);
337           g.drawLine(tipX + 1, tipY + 1, baseX2 + 1, baseY + 1);
338         }
339      }
340    
341      /**
342       * Paints a right-pointing triangle.  This method is called by the
343       * {@link #paintTriangle(Graphics, int, int, int, int, boolean)} method.
344       *
345       * @param g  the graphics device.
346       * @param x  the x-coordinate for the anchor point.
347       * @param y  the y-coordinate for the anchor point.
348       * @param size  the arrow size (depth).
349       * @param isEnabled  if <code>true</code> the arrow is drawn in the enabled
350       *                   state, otherwise it is drawn in the disabled state.
351       */
352      private void paintTriangleEast(Graphics g, int x, int y, int size,
353              boolean isEnabled)
354      {
355        int tipX = x + (size - 1);
356        int tipY = y + (size - 2) / 2;
357        int baseX = x;
358        int baseY1 = tipY - (size - 1);
359        int baseY2 = tipY + (size - 1);
360    
361        Polygon triangle = new Polygon();
362        triangle.addPoint(tipX, tipY);
363        triangle.addPoint(baseX, baseY1);
364        triangle.addPoint(baseX, baseY2);
365        if (isEnabled)
366         {
367           g.setColor(Color.DARK_GRAY);
368           g.fillPolygon(triangle);
369           g.drawPolygon(triangle);
370         }
371        else
372         {
373           g.setColor(Color.GRAY);
374           g.fillPolygon(triangle);
375           g.drawPolygon(triangle);
376           g.setColor(Color.WHITE);
377           g.drawLine(baseX + 1, baseY2, tipX, tipY + 1);
378           g.drawLine(baseX + 1, baseY2 + 1, tipX + 1, tipY + 1);
379         }
380      }
381    
382      /**
383       * Paints a left-pointing triangle.  This method is called by the
384       * {@link #paintTriangle(Graphics, int, int, int, int, boolean)} method.
385       *
386       * @param g  the graphics device.
387       * @param x  the x-coordinate for the anchor point.
388       * @param y  the y-coordinate for the anchor point.
389       * @param size  the arrow size (depth).
390       * @param isEnabled  if <code>true</code> the arrow is drawn in the enabled
391       *                   state, otherwise it is drawn in the disabled state.
392       */
393      private void paintTriangleWest(Graphics g, int x, int y, int size,
394              boolean isEnabled)
395      {
396        int tipX = x;
397        int tipY = y + (size - 2) / 2;
398        int baseX = x + (size - 1);
399        int baseY1 = tipY - (size - 1);
400        int baseY2 = tipY + (size - 1);
401    
402        Polygon triangle = new Polygon();
403        triangle.addPoint(tipX, tipY);
404        triangle.addPoint(baseX, baseY1);
405        triangle.addPoint(baseX, baseY2);
406        if (isEnabled)
407         {
408           g.setColor(Color.DARK_GRAY);
409           g.fillPolygon(triangle);
410           g.drawPolygon(triangle);
411         }
412        else
413         {
414           g.setColor(Color.GRAY);
415           g.fillPolygon(triangle);
416           g.drawPolygon(triangle);
417           g.setColor(Color.WHITE);
418           g.drawLine(baseX + 1, baseY1 + 1, baseX + 1, baseY2 + 1);
419         }
420      }
421    
422    }