001/* BasicArrowButton.java --
002   Copyright (C) 2004, 2005, 2006 Free Software Foundation, Inc.
003
004This file is part of GNU Classpath.
005
006GNU Classpath is free software; you can redistribute it and/or modify
007it under the terms of the GNU General Public License as published by
008the Free Software Foundation; either version 2, or (at your option)
009any later version.
010
011GNU Classpath is distributed in the hope that it will be useful, but
012WITHOUT ANY WARRANTY; without even the implied warranty of
013MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014General Public License for more details.
015
016You should have received a copy of the GNU General Public License
017along with GNU Classpath; see the file COPYING.  If not, write to the
018Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
01902110-1301 USA.
020
021Linking this library statically or dynamically with other modules is
022making a combined work based on this library.  Thus, the terms and
023conditions of the GNU General Public License cover the whole
024combination.
025
026As a special exception, the copyright holders of this library give you
027permission to link this library with independent modules to produce an
028executable, regardless of the license terms of these independent
029modules, and to copy and distribute the resulting executable under
030terms of your choice, provided that you also meet, for each linked
031independent module, the terms and conditions of the license of that
032module.  An independent module is a module which is not derived from
033or based on this library.  If you modify this library, you may extend
034this exception to your version of the library, but you are not
035obligated to do so.  If you do not wish to do so, delete this
036exception statement from your version. */
037
038
039package javax.swing.plaf.basic;
040
041import java.awt.Color;
042import java.awt.Dimension;
043import java.awt.Graphics;
044import java.awt.Polygon;
045
046import javax.swing.ButtonModel;
047import javax.swing.JButton;
048import 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 */
057public 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}