
Bueno, por una de esas cosas de la vida, me decidí poner a echar código y como aún no me decido a pagar $2 adicionales al mes por tener JSP y Servlets en este Blog para hacer una solución de el lado del servidor, me decidí a poner un
Applet (no me gusta Flash y no tengo licencia de su entorno de desarrollo, me da ladilla matarme pensando como hacerlo con Javascript y AJAX). Sin Java como siempre puede hacer el trabajo.
¿Un Applet para qué? Bueno,
quería poner un contador en modo regresivo el cual fuera fácil de configurar. Como no conseguí nada que me gustara entonces me decidí al final a jugar un poco con la clase JApplet.
Primero que nada el código (más abajo les comento cuales son las partes críticas):
1:package com.kodegeek.blog.applet;
2:
3:import java.awt.BorderLayout;
4:import java.awt.GridBagConstraints;
5:import java.awt.GridBagLayout;
6:import java.text.DateFormat;
7:import java.text.SimpleDateFormat;
8:import java.util.Calendar;
9:import java.util.Date;
10:import java.util.ResourceBundle;
11:
12:import javax.swing.JPanel;
13:import javax.swing.JApplet;
14:import javax.swing.JProgressBar;
15:import javax.swing.JLabel;
16:import javax.swing.JToolTip;
17:import javax.swing.JTextField;
18:import javax.swing.SwingUtilities;
19:
20:public final class RegresiveCounter extends JApplet implements Runnable {
21:
22: private JPanel jContentPane = null;
23: private JProgressBar jProgressBar = null;
24: private JLabel jLabelLefTime = null;
25: private JLabel jLabelEndTime = null;
26: private JToolTip jToolTip = null;
27: private JTextField jTextFieldLeftTime = null;
28: private JTextField jTextFieldEndTime = null;
29: GridBagConstraints constraint = null;
30: private static final DateFormat DATE_FMT =
31: DateFormat.getDateInstance();
32:
33: private final static int ONE_SEC = 1;
34: private final static long MS_PER_SEC = 1000;
35: private final static long MS_PER_MIN = 1000 * 60;
36: private final static long MS_PER_HOUR = 1000 * 60 * 60;
37: private final static long MS_PER_DAY = 1000 * 60 * 60 * 24;
38:
39: private Calendar now;
40: private Calendar start;
41: private Calendar end;
42:
43: private SimpleDateFormat endTimeFormatter;
44:
45: /**
46: * The serial id of this class.
47: */
48: public static final long serialVersionUID = 200601141L;
49:
50: private static ResourceBundle BUNDLE;
51:
52: {
53: BUNDLE =
54: ResourceBundle.getBundle(RegresiveCounter.class.getName());
55:
56: }
57:
58: /**
59: * This is the default constructor
60: */
61: public RegresiveCounter() {
62: super();
63: getRootPane().putClientProperty("defeatSystemEventQueueCheck",
Boolean.TRUE);
64: constraint = new GridBagConstraints();
65: endTimeFormatter =
66: new SimpleDateFormat(
BUNDLE.getString("RegresiveCounter.endTimeLabel.format"));
67: }
68:
69: /**
70: * This method initializes the applet
71: *
72: * @return void
73: */
74: public void init() {
75: this.setSize(550, 200);
76: this.setContentPane(getJContentPane());
77: showStatus(BUNDLE.getString("RegresiveCounter.status"));
78: end = Calendar.getInstance();
79: now = Calendar.getInstance();
80: start = Calendar.getInstance();
81: }
82:
83: /**
84: * This method initializes jContentPane
85: *
86: * @return javax.swing.JPanel
87: */
88: private JPanel getJContentPane() {
89: if (jContentPane == null) {
90:
91: jContentPane = new JPanel();
92: jContentPane.setLayout(new BorderLayout());
93:
94: JPanel upperPanel = new JPanel();
95: upperPanel.setLayout(new GridBagLayout());
96:
97: // Add elements to the upper panel
98: constraint.gridx = 0;
99: constraint.gridy = 0;
100: constraint.anchor = GridBagConstraints.LAST_LINE_END;
101: upperPanel.add(getJlabelLefTime(), constraint);
102:
103: constraint.gridx = 1;
104: constraint.gridy = 0;
105: constraint.anchor = GridBagConstraints.LAST_LINE_START;
106: upperPanel.add(getJTextFieldLeftTime(), constraint);
107:
108: constraint.gridx = 0;
109: constraint.gridy = 1;
110: constraint.anchor = GridBagConstraints.LAST_LINE_END;
111: upperPanel.add(getJlabelEndTime(), constraint);
112:
113: constraint.gridx = 1;
114: constraint.gridy = 1;
115: constraint.anchor = GridBagConstraints.LAST_LINE_START;
116: upperPanel.add(getJTextFieldEndTime(), constraint);
117:
118: // Add elements to the main panel
119: jContentPane.add(upperPanel, BorderLayout.NORTH);
120: jContentPane.add(getJProgressBar(), BorderLayout.SOUTH);
121: }
122: return jContentPane;
123: }
124:
125: /**
126: * Get the app left side label
127: * @return
128: */
129: private JLabel getJlabelLefTime() {
130: if (jLabelLefTime == null) {
131: jLabelLefTime = new JLabel();
132: if (getParameter("lefTimelabel") != null) {
133: jLabelLefTime.setText(getParameter("lefTimelabel")
+ ":");
134: } else {
135: jLabelLefTime.setText(
BUNDLE.getString("RegresiveCounter.lefTimelabel") + ":");
136: }
137: }
138: return jLabelLefTime;
139: }
140:
141: /**
142: * Get the app left side label
143: * @return
144: */
145: private JLabel getJlabelEndTime() {
146: if (jLabelEndTime == null) {
147: jLabelEndTime = new JLabel();
148: if (getParameter("endTimeLabel") != null) {
149: jLabelEndTime.setText(getParameter("endTimeLabel")
+ ":");
150: } else {
151: jLabelEndTime.setText(
BUNDLE.getString("RegresiveCounter.endTimeLabel") + ":");
152: }
153: }
154: return jLabelEndTime;
155: }
156:
157: /**
158: * This method initializes jProgressBar
159: *
160: * @return javax.swing.JProgressBar
161: */
162: private JProgressBar getJProgressBar() {
163: if (jProgressBar == null) {
164: jProgressBar = new JProgressBar();
165: jProgressBar.setMinimum(1);
166: }
167: return jProgressBar;
168: }
169:
170: /**
171: * This method initializes jTextFieldLeftTime
172: *
173: * @return javax.swing.JTextField
174: */
175: private JTextField getJTextFieldLeftTime() {
176: if (jTextFieldLeftTime == null) {
177: jTextFieldLeftTime = new JTextField();
178: jTextFieldLeftTime.setText(
BUNDLE.getString("RegresiveCounter.remainingTime"));
179: jTextFieldLeftTime.setEditable(false);
180: jToolTip = new JToolTip();
181: jToolTip.setTipText(
BUNDLE.getString("RegresiveCounter.remainingTime.tooltip"));
182: jToolTip.setComponent(jTextFieldLeftTime);
183: }
184: return jTextFieldLeftTime;
185: }
186:
187: /**
188: * This method initializes jTextFieldLeftTime
189: *
190: * @return javax.swing.JTextField
191: */
192: private JTextField getJTextFieldEndTime() {
193: if (jTextFieldEndTime == null) {
194: jTextFieldEndTime = new JTextField();
195: jTextFieldEndTime.setText("");
196: jTextFieldEndTime.setEditable(false);
197: }
198: return jTextFieldEndTime;
199: }
200:
201: /**
202: * Hint for the user about how to use the Applet
203: * @return 3D Array of array with the information about how to use the applet
204: */
205: public String[][] getParameterInfo() {
206: String[][] info = {
207: {"initDate", "date string",
BUNDLE.getString("RegresiveCounter.help.initDate")},
208: {"endDate", "date string",
BUNDLE.getString("RegresiveCounter.help.endDate")},
209: {"title", "any string",
BUNDLE.getString("RegresiveCounter.help.title")},
210: {"endTimeLabel", "any string",
BUNDLE.getString("RegresiveCounter.help.endTimeLabel")},
211: {"lefTimelabel", "any string",
BUNDLE.getString("RegresiveCounter.help.lefTimelabel")}
212: };
213: return info;
214: }
215:
216: /**
217: * Start the countdown and update the display.
218: */
219: public void start() {
220: super.start();
221: try {
222: SwingUtilities.invokeLater(new Runnable() {
223: public void run() {
224:
225: if (getParameter("initDate") == null) {
226: throw new
227: IllegalArgumentException(
BUNDLE.getString("RegresiveCounter.error.missingArgument") + ": initDate");
228: }
229: if (getParameter("endDate") == null) {
230: throw new
231: IllegalArgumentException(
BUNDLE.getString("RegresiveCounter.error.missingArgument") + ": endDate");
232: }
233:
234: try {
235: Date initDate =
DATE_FMT.parse(getParameter("initDate"));
236: Date endDate =
DATE_FMT.parse(getParameter("endDate"));
237:
238: validateDate(initDate, endDate);
239: validateDate(now.getTime(), endDate);
240: // Adjust the calendars
241: end.setTime(endDate);
242: start.setTime(initDate);
243: } catch (Exception exp) {
244: throw new RuntimeException(exp);
245: }
246: }
247: });
248:
249: // Run the upate thread
250: Thread thread = new Thread(this);
251: thread.start();
252:
253: } catch (Exception exp) {
254: getJTextFieldLeftTime().setText(exp.getMessage());
255: throw new RuntimeException(exp);
256: } finally {
257: validate();
258: }
259: }
260:
261: /**
262: * Return the date tokens on the formatted form
263: * @param time
264: * @return
265: */
266: public StringBuffer createFormattedRemTimeString(int [] time) {
267: StringBuffer fTime = new StringBuffer();
268: for (int i = 0; i < time.length; i++) {
269: fTime.append(time[i]);
270: if (i < time.length - 1) {
271: fTime.append(":");
272: }
273: }
274: return(fTime);
275: }
276:
277: /**
278: * Check if there is no overlap with two boundary dates
279: * @param init
280: * @param end
281: * @throws IllegalArgumentException If init is greather than end
282: */
283: private void validateDate(Date init, Date end) throws IllegalArgumentException {
284: if (init.after(end)) {
285: throw new
286: IllegalArgumentException(
BUNDLE.getString("RegresiveCounter.error.invalidDate"));
287: }
288: }
289:
290: /**
291: * Return an array containing the number of hours, minutes and seconds
292: * for a given number of miliseconds.
293: * @param timeInMiliSeconds - Normally obtained by using getTime()
294: * @return Number of miliseconds now properly formatted
295: * <ul>
296: * <li> 0 - Days
297: * <li> 1 - Hours
298: * <li> 2 - minutes
299: * <li> 3 - Seconds
300: * </ul>
301: */
302: private int [] decompTime(long timeInMiliSeconds) {
303: int [] timePieces = new int[4];
304: timePieces[0] = (int) (timeInMiliSeconds / MS_PER_DAY); // Days
305: timeInMiliSeconds = timeInMiliSeconds - (timePieces[0] * MS_PER_DAY);
306: timePieces[1] = (int) (timeInMiliSeconds / MS_PER_HOUR); // Hours
307: timeInMiliSeconds = timeInMiliSeconds - (timePieces[1] * MS_PER_HOUR);
308: timePieces[2] = (int) (timeInMiliSeconds / MS_PER_MIN); // Minutes
309: timeInMiliSeconds = timeInMiliSeconds - (timePieces[2] * MS_PER_MIN);
310: timePieces[3] = (int) (timeInMiliSeconds / MS_PER_SEC); // Seconds
311: return timePieces;
312: }
313:
314: /**
315: * Update the contents of the GUI on a separate thread
316: */
317: public void run() {
318: while(now.before(end)) {
319: now.add(Calendar.SECOND, ONE_SEC); // Add 1 second
320: long remaining = end.getTimeInMillis() - now.getTimeInMillis();
321: long used = now.getTimeInMillis() - start.getTimeInMillis();
322: int [] rTime = decompTime(remaining);
323: int [] uTime = decompTime(used);
324: StringBuffer fTime = createFormattedRemTimeString(rTime);
325: getJTextFieldLeftTime().setText(fTime.toString());
326: getJTextFieldEndTime().setText(
endTimeFormatter.format(end.getTime()));
327: getJProgressBar().setMaximum(rTime[0] + uTime[0]);
328: getJProgressBar().setValue(uTime[0]);
329:
330: try {
331: Thread.sleep(MS_PER_SEC);
332: } catch (InterruptedException intExp) {
333: throw new RuntimeException(intExp);
334: } finally {
335: validate();
336: fTime.setLength(0);
337: }
338: }
339: }
340:
341: /**
342: * Get information about the apple
343: * @return
344: */
345: public String getAppletInfo() {
346: return BUNDLE.getString("RegresiveCounter.info")
+ ", " + BUNDLE.getString("RegresiveCounter.status");
347: }
348:}
Básicamente este es el plan de acción:
- Obtener la fecha de inicio y la fecha final de el usuario. Para eso utilizamos parámetros desde la etiqueta APPLET en el HTML.
- Calculamos la diferencia en milisegundos entre la fecha final y la fecha actual. Usando ese valor (cantidad de milisegundos) lo convertimos a días, horas, minutos y segundos. Como es una operación que realizamos con frecuencia le dedicamos una método privado (línea 302).
- Calculamos la cantidad de días entre la fecha final y la fecha inicial. Con eso definimos los límites de la barra de progreso (línea 327).
- Repetimos el proceso de calcular la fecha mientras la fecha actual sea menor que la fecha de destino. Para ello incrementamos la fecha actual en un segundo a la vez, para luego calcular la diferencia explicada en el paso 2.
No hay mucho que agregar aquí, excepto que decidí probar la el método '
SwingUtilities.invokeLater' (
línea 222) el cual según la gente de Sun puede ser utilizado para prevenir problemas de redibujado de pantalla cuando se usa Swing. las actualizaciones de la interfaz gráfica las hago desde otra hebra (
línea 250).
Al final la llamada es bastante sencilla:
1:<h3 class="date-header">Cuenta regresiva para la venida de el hibrido.</h3>
2:<APPLET
3:CODE="com.kodegeek.blog.applet.RegresiveCounter.class"
4:WIDTH="550"
5:HEIGHT="200"
6:ARCHIVE="RegresiveCounter.jar"
7:ALT="Contando los días para la venida de el hibrido">
8:<PARAM NAME="initDate" VALUE="2006-04-09">
9:<PARAM NAME="endDate" VALUE="2007-01-14">
10:<PARAM NAME="title" VALUE="¡Ya viene el hibrido!">
11:<PARAM NAME="label" VALUE="Tiempo faltante">
12:Tu navegador no soporta la etiqueta applet!
13:</APPLET>
14:
Los
binarios y una página de HTML para que los utilice en su sitio web . El código fuente
está en CVS y lo pueden compilar usando
Ant. El Applet sólo funciona con el JDK 1.5,
el cual se lo puede bajar e instalar en un momento desde acá.
Como siempre el código es gratuito, viene sin garantías. Creo que corre bien y lo invito a que lo use en su sitio web. Puede verlo en
funcionamiento aquí.
Sin embargo quedan preguntas sin responder: ¿Porqué el contador? ¿Qué es un hibrido?. Paciencia, estas preguntas serán respondidas a su debido tiempo :D
Buscar en Technorati:
hibrido,
venezuela,
java,
linux,
Open Source,
AppletBuscar en Blogalaxia:
hibrido,
venezuela,
java,
linux,
Open Source,
Applet