View Javadoc

1   /*
2    * Copyright (c) 2003-2008 by Cosylab d. d.
3    *
4    * This file is part of CosyBeans-Common.
5    *
6    * CosyBeans-Common is free software: you can redistribute it and/or modify
7    * it under the terms of the GNU General Public License as published by
8    * the Free Software Foundation, either version 3 of the License, or
9    * (at your option) any later version.
10   *
11   * CosyBeans-Common is distributed in the hope that it will be useful,
12   * but WITHOUT ANY WARRANTY; without even the implied warranty of
13   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14   * GNU General Public License for more details.
15   *
16   * You should have received a copy of the GNU General Public License
17   * along with CosyBeans-Common.  If not, see <http://www.gnu.org/licenses/>.
18   */
19  
20  package com.cosylab.gui.components.wheelswitch;
21  
22  import com.cosylab.gui.components.SimpleButton;
23  import com.cosylab.gui.components.util.ColorHelper;
24  import com.cosylab.gui.components.util.PaintHelper;
25  
26  import java.awt.AlphaComposite;
27  import java.awt.Dimension;
28  import java.awt.GradientPaint;
29  import java.awt.Graphics;
30  import java.awt.Graphics2D;
31  import java.awt.Paint;
32  import java.awt.image.BufferedImage;
33  
34  import java.util.HashMap;
35  import java.util.Timer;
36  import java.util.TimerTask;
37  
38  import javax.swing.border.Border;
39  import javax.swing.border.LineBorder;
40  
41  
42  /**
43   * An extension of <code>SimpleButton</code> displaying one digit (character).
44   *
45   * @author <a href="mailto:jernej.kamenik@cosylab.com">Jernej Kamenik</a>
46   * @version $id$
47   */
48  public abstract class Digit extends SimpleButton
49  {
50  	/*
51  	 * Used for the animation of the switching of the displayed symbols
52  	 * on the digit.
53  	 */
54  	private class AnimationTask extends TimerTask
55  	{
56  		/**
57  		 * @see java.util.TimerTask#run()
58  		 */
59  		public void run()
60  		{
61  			repaint();
62  
63  			if (animationCompleted >= 1f) {
64  				cancel();
65  			}
66  
67  			animationCompleted += 0.2f;
68  		}
69  	}
70  
71  	private boolean animated = false;
72  	private static Timer animationTimer = null;
73  	private static HashMap<Dimension, HashMap<String, BufferedImage>> images = null;
74  	private static HashMap<Dimension, BufferedImage[]> backgroundImages = null;
75  	private static Border enhancedPressedBorder = new LineBorder(ColorHelper
76  		    .getCosyControlShadow());
77  	private static Border pressedBorder = new LineBorder(ColorHelper
78  		    .getCosyControlShadow());
79  	private static Border enhancedSelectedBorder = new LineBorder(ColorHelper
80  		    .getCosyControlShadow());
81  	private static Border selectedBorder = new LineBorder(ColorHelper
82  		    .getCosyControlShadow());
83  	private static Border enhancedBorder = new LineBorder(ColorHelper
84  		    .getCosyControlDarkShadow());
85  	private static Border border = new LineBorder(ColorHelper.getControlShadow());
86  	private String newText = null;
87  	private String oldText = null;
88  	private boolean enhanced = false;
89  	private boolean sel = false;
90  	private boolean tilting = false;
91  	private float animationCompleted = 1.f;
92  
93  	/**
94  	 * Constructor for Digit creates an empty Digit.
95  	 */
96  	public Digit()
97  	{
98  		super();
99  
100 		if (images == null) {
101 			images = new HashMap<Dimension, HashMap<String, BufferedImage>>();
102 		}
103 
104 		if (backgroundImages == null) {
105 			backgroundImages = new HashMap<Dimension, BufferedImage[]>();
106 		}
107 
108 		setResizable(true);
109 		setFocusable(false);
110 		setColumns(1);
111 		init();
112 	}
113 
114 	/**
115 	 * Sets the enhancment mode of the <code>Digit</code>. When enhanced, the
116 	 * digit is painted using anti-aliasing rendering hints.
117 	 *
118 	 * @param newEnhanced
119 	 */
120 	public void setEnhanced(boolean newEnhanced)
121 	{
122 		if (newEnhanced == enhanced) {
123 			return;
124 		}
125 
126 		enhanced = newEnhanced;
127 		init();
128 		repaint();
129 	}
130 
131 	/**
132 	 * Checks whether the <code>Digit</code> is enhanced.
133 	 *
134 	 * @return boolean
135 	 */
136 	public boolean isEnhanced()
137 	{
138 		return enhanced;
139 	}
140 
141 	/**
142 	 * Sets or removes the selection from the <code>Digit</code>.
143 	 *
144 	 * @param newSelected
145 	 */
146 	public void setSelected(boolean newSelected)
147 	{
148 		if (sel == newSelected) {
149 			return;
150 		}
151 
152 		sel = newSelected;
153 
154 		//Debug.out(this+" selected="+newSelected);
155 		init();
156 		repaint();
157 	}
158 
159 	/**
160 	 * Checks for selection of the <code>Digit</code>.
161 	 *
162 	 * @return true if the <code>Digit</code> is selected, false otherwise.
163 	 */
164 	public boolean isSelected()
165 	{
166 		return sel;
167 	}
168 
169 	/**
170 	 * This method has been overriden to implement animated transitions between
171 	 * displayed text images. It sets the new text and starts the animation of
172 	 * the transition (if it is not already running).
173 	 *
174 	 * @see JLabel#setText(java.lang.String)
175 	 */
176 	public synchronized void setText(String newText)
177 	{
178 		if (newText != null
179 		    && (newText.equals(getText()) || newText.length() != 1)) {
180 			return;
181 		}
182 
183 		this.oldText = getText();
184 		this.newText = newText;
185 		super.setText(newText);
186 
187 		if (animated) {
188 			if (animationCompleted >= 1f) {
189 				animationCompleted = 0f;
190 
191 				if (animationTimer == null) {
192 					animationTimer = new Timer();
193 				}
194 
195 				// TODO this is workaround for applet, has to be investigated
196 				try {
197 					animationTimer.schedule(new AnimationTask(), 0, 20);
198 				} catch (IllegalStateException e) {
199 					animationTimer = new Timer();
200 					animationTimer.schedule(new AnimationTask(), 0, 20);
201 				}
202 			}
203 		}
204 	}
205 
206 	/**
207 	 * Sets the tilting.
208 	 *
209 	 * @param tilting The tilting to set
210 	 */
211 	public void setTilting(boolean tilting)
212 	{
213 		this.tilting = tilting;
214 	}
215 
216 	/**
217 	 * Returns the tilting.
218 	 *
219 	 * @return boolean
220 	 */
221 	public boolean isTilting()
222 	{
223 		return tilting;
224 	}
225 
226 	/**
227 	 * This method was overriden to implement enhanced anti-aliasing display
228 	 * features as well as animated transitions.
229 	 *
230 	 * @param g 
231 	 */
232 	public void paintComponent(Graphics g)
233 	{
234 		int width = getWidth();
235 		int height = getHeight();
236 		Dimension size = getSize();
237 
238 		if (enhanced) {
239 			Graphics2D g2D;
240 			BufferedImage image;
241 			Paint paint;
242 			boolean presel = (sel || isPressed());
243 
244 			if (backgroundImages.get(size) == null) {
245 				backgroundImages.put(size, new BufferedImage[2]);
246 			}
247 
248 			if ((((Object[])backgroundImages.get(size))[0] == null && !presel)
249 			    || (((Object[])backgroundImages.get(size))[1] == null && presel)) {
250 				image = new BufferedImage(width, height,
251 					    BufferedImage.TYPE_4BYTE_ABGR);
252 				g2D = image.createGraphics();
253 
254 				if (presel) {
255 					paint = new GradientPaint(0f, 0f,
256 						    ColorHelper.getCosyControl(), (float)width,
257 						    (float)height, ColorHelper.getCosyControlShadow());
258 					g2D.setPaint(paint);
259 					g2D.fillRect(0, 0, width, height);
260 					g2D.setComposite(AlphaComposite.getInstance(
261 					        AlphaComposite.SRC_OVER, 0.5f));
262 					paint = new GradientPaint((float)width / 2f, 0f,
263 						    ColorHelper.getCosyControlShadow(),
264 						    (float)width / 2f, (float)height / 2f,
265 						    ColorHelper.getCosyControl());
266 					g2D.setPaint(paint);
267 					g2D.fillRect(0, 0, width, height / 2);
268 					paint = new GradientPaint((float)width / 2f,
269 						    (float)height / 2f, ColorHelper.getCosyControl(),
270 						    (float)width / 2f, (float)height,
271 						    ColorHelper.getCosyControlShadow());
272 					g2D.setPaint(paint);
273 					g2D.fillRect(0, height / 2, width, height);
274 					(backgroundImages.get(getSize()))[1] = image;
275 				} else {
276 					paint = new GradientPaint(0f, 0f,
277 						    ColorHelper.getCosyControlHighlight(),
278 						    (float)width, (float)height,
279 						    ColorHelper.getCosyControl());
280 					g2D.setPaint(paint);
281 					g2D.fillRect(0, 0, width, height);
282 					g2D.setComposite(AlphaComposite.getInstance(
283 					        AlphaComposite.SRC_OVER, 0.5f));
284 					paint = new GradientPaint((float)width / 2f, 0f,
285 						    ColorHelper.getCosyControl(), (float)width / 2f,
286 						    (float)height / 2f,
287 						    ColorHelper.getCosyControlHighlight());
288 					g2D.setPaint(paint);
289 					g2D.fillRect(0, 0, width, height / 2);
290 					paint = new GradientPaint((float)width / 2f,
291 						    (float)height / 2f,
292 						    ColorHelper.getCosyControlHighlight(),
293 						    (float)width / 2f, (float)height,
294 						    ColorHelper.getCosyControl());
295 					g2D.setPaint(paint);
296 					g2D.fillRect(0, height / 2, width, height);
297 					(backgroundImages.get(size))[0] = image;
298 				}
299 			} else {
300 				if (presel) {
301 					image = backgroundImages.get(size)[1];
302 				} else {
303 					image = backgroundImages.get(size)[0];
304 				}
305 			}
306 
307 			g2D = (Graphics2D)g;
308 			g2D.drawImage(image, null, 0, 0);
309 			g2D.addRenderingHints(PaintHelper.getAntialiasingHints());
310 
311 			if (animated && animationCompleted < 1.f) {
312 				if (images.get(size) == null) {
313 					images.put(size, new HashMap<String, BufferedImage>());
314 				}
315 
316 				if ((images.get(size)).get(newText) == null) {
317 					image = new BufferedImage(width, height,
318 						    BufferedImage.TYPE_4BYTE_ABGR);
319 
320 					Graphics2D gr = image.createGraphics();
321 					gr.addRenderingHints(PaintHelper.getAntialiasingHints());
322 					gr.setFont(getFont());
323 					super.paintComponent(gr);
324 					images.get(size).put(newText, image);
325 				}
326 
327 				paintDigitTransition(images.get(getSize()).get(oldText),
328 				    images.get(getSize()).get(newText), g2D, animationCompleted);
329 				super.paintBorder(g2D);
330 			} else {
331 				super.paintComponent(g2D);
332 			}
333 		} else {
334 			super.paintComponent(g);
335 		}
336 
337 		if (tilting) {
338 			PaintHelper.paintRectangle(g, 0, 0, width - 1, height - 1,
339 			    ColorHelper.getEmergencyOutline(), 1);
340 		}
341 	}
342 
343 	/**
344 	 * (Re)Initializes the <code>Digit</code>. Sets the border, background and
345 	 * foreground colors and opacity of the digit depending on the current
346 	 * selection state of the digit.
347 	 */
348 	protected void init()
349 	{
350 		setHorizontalAlignment(0);
351 
352 		if (enhanced) {
353 			setOpaque(false);
354 			setPressedForeground(ColorHelper.getText());
355 			setPressedBorder(enhancedPressedBorder);
356 
357 			if (sel) {
358 				setForeground(ColorHelper.getText());
359 				setBorder(enhancedSelectedBorder);
360 			} else if (isEnabled()) {
361 				setForeground(ColorHelper.getControlText());
362 				setBorder(enhancedBorder);
363 			} else {
364 				setForeground(ColorHelper.getControlShadow());
365 				setBorder(enhancedBorder);
366 			}
367 		} else {
368 			setOpaque(true);
369 			setPressedBackground(ColorHelper.getTextHighlight());
370 			setPressedForeground(ColorHelper.getText());
371 			setPressedBorder(pressedBorder);
372 
373 			if (sel) {
374 				setBackground(ColorHelper.getTextHighlight());
375 				setForeground(ColorHelper.getText());
376 				setBorder(selectedBorder);
377 			} else if (isEnabled()) {
378 				setBackground(ColorHelper.getCosyControlHighlight());
379 				setForeground(ColorHelper.getControlText());
380 				setBorder(border);
381 			} else {
382 				setBackground(ColorHelper.getCosyControl());
383 				setForeground(ColorHelper.getControlText());
384 				setBorder(border);
385 			}
386 		}
387 	}
388 
389 	/**
390 	 * The method combines two images and paints them onto the selected
391 	 * <code>Graphics</code> object based on the value of parameter between 0
392 	 * and 1.f. When paramter equals 0, oldImage should be painted completely
393 	 * and newImage not at all, and opposite when parameter equals 1.f.
394 	 * Descedants of <code>Digit</code> should override this method to
395 	 * implement different types of image transitions.
396 	 *
397 	 * @param oldImage
398 	 * @param newImage
399 	 * @param g Graphics object on which to paint the two images.
400 	 * @param parameter float value between 0 and 1.f.
401 	 */
402 	protected void paintDigitTransition(BufferedImage oldImage,
403 	    BufferedImage newImage, Graphics g, float parameter)
404 	{
405 		super.paintComponent(g);
406 
407 		Graphics2D g2D = (Graphics2D)g;
408 		g2D.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
409 		        Math.abs(1.f - parameter)));
410 
411 		if (oldImage != null) {
412 			g2D.drawImage(oldImage, null, 0, 0);
413 		}
414 
415 		g2D.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
416 		        parameter % 1));
417 
418 		if (newImage != null) {
419 			g2D.drawImage(newImage, null, 0, 0);
420 		}
421 	}
422 
423 	/**
424 	 * @param b
425 	 */
426 	public void setAnimated(boolean b) {
427 		this.animated = b;
428 	}
429 }
430 
431 /* __oOo__ */