001: package snow.utils;
002:
003: import java.util.prefs.Preferences;
004: import javax.swing.border.EmptyBorder;
005: import java.awt.EventQueue;
006: import snow.utils.gui.*;
007: import snow.sortabletable.*;
008: import snow.utils.storage.*;
009: import java.awt.TrayIcon;
010: import java.awt.SystemTray;
011: import java.awt.FlowLayout;
012: import javax.swing.table.*;
013: import java.awt.Component;
014: import java.awt.Insets;
015: import java.awt.event.*;
016: import java.io.*;
017: import java.util.*;
018: import java.text.*;
019: import java.awt.BorderLayout;
020: import javax.swing.*;
021:
022: /** Useful timer remainder.
023: * Can be used for any purpose.
024: */
025: public final class Countdown extends JFrame {
026:
027: // has to be set to true prior to getInstance, if standalone
028: private static boolean standalone = false;
029:
030: static final File storageFile = new File(System
031: .getProperty("user.home"), ".tide_global/countdowns.vec");
032:
033: final private CountdownTable cdt = new CountdownTable();
034: final SortableTableModel stm;
035: final JTable table;
036: Renderer rend = new Renderer();
037:
038: // either
039: final DateFormat df = new SimpleDateFormat("dd.MM.yyyy HH:mm");
040: //final DateFormat df2 = new SimpleDateFormat("dd.MM.yyyy HH:mm");
041:
042: private Vector<CountdownData> countdowns = new Vector<CountdownData>();
043:
044: final private java.util.Timer updaterTimer = new java.util.Timer(
045: true);
046:
047: private static Countdown instance;
048:
049: public static synchronized Countdown getInstance() {
050: if (instance != null)
051: return instance;
052: instance = new Countdown();
053: return instance;
054: }
055:
056: private static TrayIcon trayIcon;
057:
058: /** Only if active timers are present.
059: * Should be called at each changes.
060: */
061: public static void installTrayIfNecessary() {
062: if (!lookIfActiveTimersArePresent())
063: return;
064: if (!SystemTray.isSupported())
065: return;
066: if (trayIcon != null)
067: return;
068:
069: int w = (int) SystemTray.getSystemTray().getTrayIconSize()
070: .getWidth();
071: trayIcon = new TrayIcon(Icons.createImage(new Icons.TimerIcon(
072: w, w, true)), "Countdown");
073: try {
074: SystemTray.getSystemTray().add(trayIcon);
075: } catch (Exception e) {
076: e.printStackTrace();
077: }
078: trayIcon.addMouseListener(new MouseAdapter() {
079: @Override
080: public void mousePressed(MouseEvent me) {
081: }
082:
083: @Override
084: public void mouseReleased(MouseEvent me) {
085: }
086:
087: @Override
088: public void mouseClicked(MouseEvent me) {
089: getInstance().setVisible(true);
090: }
091: });
092:
093: // initialize the counter
094: getInstance();
095: }
096:
097: // NOt set to visible, is silent if no counter active
098: private Countdown() {
099: super ("Countdowns");
100: this .setIconImage(Icons.createImage(new Icons.TimerIcon(16, 16,
101: true)));
102:
103: instance = this ;
104:
105: setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
106:
107: load();
108:
109: stm = new SortableTableModel(cdt);
110: table = new JTable(stm);
111: table.setDefaultRenderer(CountdownData.class, rend);
112: stm.installGUI(table);
113: add(new JScrollPane(table), BorderLayout.CENTER);
114:
115: table.addMouseListener(new MouseAdapter() {
116: @Override
117: public void mousePressed(MouseEvent me) {
118: }
119:
120: @Override
121: public void mouseReleased(MouseEvent me) {
122: }
123:
124: @Override
125: public void mouseClicked(MouseEvent me) {
126: if (me.getClickCount() == 2) {
127: int[] sel = table.getSelectedRows();
128: if (sel.length != 1)
129: return;
130: int ed = stm.getIndexInUnsortedFromTablePos(sel[0]);
131: CountdownData cd = countdowns.get(ed);
132: CountdownEditor cde = new CountdownEditor(cd);
133: cdt.update();
134: save();
135: }
136: }
137:
138: });
139:
140: JPanel cp = new JPanel();
141: cp.setBorder(new EmptyBorder(2, 2, 2, 2));
142: cp.setLayout(new BoxLayout(cp, BoxLayout.X_AXIS));
143: add(cp, BorderLayout.NORTH);
144:
145: JButton add = new JButton("add new countdown", Icons.sharedPlus);
146: add.setMargin(new Insets(1, 1, 1, 1));
147: cp.add(add);
148: add.addActionListener(new ActionListener() {
149: public void actionPerformed(ActionEvent ae) {
150: CountdownData cd = new CountdownData();
151: countdowns.add(cd);
152: cdt.update();
153: CountdownEditor cde = new CountdownEditor(cd);
154: cdt.update();
155: save();
156: }
157: });
158:
159: JButton remove = new JButton("remove selected",
160: Icons.sharedCross);
161: remove.setMargin(new Insets(1, 1, 1, 1));
162: cp.add(Box.createHorizontalStrut(5));
163: cp.add(remove);
164: remove.addActionListener(new ActionListener() {
165: public void actionPerformed(ActionEvent ae) {
166: int[] sel = table.getSelectedRows();
167: //if(sel.length!=1) return;
168: List<CountdownData> rem = new ArrayList<CountdownData>();
169: for (int si : sel) {
170: int ed = stm.getIndexInUnsortedFromTablePos(si);
171: CountdownData cd = countdowns.get(ed);
172: rem.add(cd);
173: }
174: countdowns.removeAll(rem);
175:
176: cdt.update();
177: save();
178: }
179: });
180:
181: if (standalone) {
182: JButton close = new JButton("quit", Icons.sharedCross);
183: close.setMargin(new Insets(1, 1, 1, 1));
184: cp.add(Box.createHorizontalGlue());
185: cp.add(close);
186: close.addActionListener(new ActionListener() {
187: public void actionPerformed(ActionEvent ae) {
188: System.exit(0);
189: }
190: });
191: } else {
192: // runs within tide.
193:
194: boolean auto = Preferences.userRoot().getBoolean(
195: "tide_countdown_autostart", true);
196: final JCheckBox cbAutoStart = new JCheckBox("Autostart",
197: auto);
198: cp.add(Box.createHorizontalGlue());
199: cp.add(cbAutoStart);
200: cbAutoStart.addActionListener(new ActionListener() {
201: public void actionPerformed(ActionEvent ae) {
202: Preferences.userRoot().putBoolean(
203: "tide_countdown_autostart",
204: cbAutoStart.isSelected());
205: }
206: });
207:
208: JButton stop = new JButton("stop", Icons.sharedCross);
209: stop.setMargin(new Insets(1, 1, 1, 1));
210: cp.add(stop);
211: stop.addActionListener(new ActionListener() {
212: public void actionPerformed(ActionEvent ae) {
213: try {
214: instance = null;
215: if (trayIcon != null) {
216: SystemTray.getSystemTray().remove(trayIcon);
217: updaterTimer.cancel();
218: trayIcon = null;
219: }
220: } catch (Exception e) {
221: e.printStackTrace();
222: }
223: }
224: });
225: }
226:
227: MultiSearchPanel searchPanel = new MultiSearchPanel("Filter: ",
228: null, stm);
229: add(searchPanel, BorderLayout.SOUTH);
230:
231: setSize(600, 400);
232: this .setLocationRelativeTo(null);
233:
234: updaterTimer.schedule(new TimerTask() {
235: public final void run() {
236: //cdt.update(); // no, a repaint is enough
237: if (isVisible()) {
238: table.repaint();
239: }
240:
241: EventQueue.invokeLater(new Runnable() {
242: public void run() {
243: lookIfReached();
244: }
245: });
246:
247: }
248:
249: }, 2000L, 1000L);
250:
251: installTrayIfNecessary();
252: }
253:
254: /** Called in the timer.
255: */
256: private void lookIfReached() {
257: for (CountdownData cd : countdowns) {
258: if (cd.hasBeenAlerted)
259: continue;
260: if (cd.shouldAlertNow()) {
261: cd.setHasBeenAlerted(); // eventually resets the counter to the next recurrence, if not once
262:
263: if (trayIcon != null) {
264: trayIcon.displayMessage("Countdown done !", cd.name
265: + "\n" + cd.description,
266: TrayIcon.MessageType.INFO);
267: }
268:
269: JOptionPane.showMessageDialog(null, "" + cd.name
270: + ": DONE !\n" + cd.description, cd.name,
271: JOptionPane.INFORMATION_MESSAGE);
272:
273: save();
274: }
275: }
276: }
277:
278: /* Installs an independant checker...
279: *
280: public static void external_LookAndWarnCountdownEnds()
281: {
282: }*/
283:
284: class CountdownEditor extends JDialog {
285: private final JTextField nameTF = new JTextField("", 12);
286: private final JTextField startDateTF = new JTextField("", 12);
287: private final JTextField dateTF = new JTextField("", 12);
288: // private final JTextArea descrTF = new JTextArea("", 4, 30);
289:
290: private final JTextArea detailsTF = new JTextArea("", 4, 30);
291: private JCheckBox alertCB = new JCheckBox();
292: private JComboBox reccurenceCB = new JComboBox(new Object[] {
293: "Once", "Daily", "Weekly", "Monthly", "Quarterly",
294: "Yearly" });
295: final private java.util.Timer remTimer = new java.util.Timer(
296: true);
297:
298: public CountdownEditor(CountdownData cd) {
299: super (Countdown.this , "Countdown editor", true);
300: setData(cd);
301:
302: JPanel cp = new JPanel();
303: add(cp, BorderLayout.CENTER);
304: GridLayout3 gl3 = new GridLayout3(2, cp);
305:
306: gl3.add("Name");
307: gl3.add(nameTF);
308:
309: gl3.add("Start date (dd.MM.yyyy [HH:mm])");
310: gl3.add(startDateTF);
311:
312: gl3.add("Target date (dd.MM.yyyy [HH:mm])");
313: gl3.add(dateTF);
314:
315: gl3.add("Alert when reached (popup)");
316: gl3.add(alertCB);
317:
318: gl3.add("Reccurence");
319: gl3.add(reccurenceCB);
320:
321: gl3.addSeparator();
322: //gl3.add("Remaining time");
323: detailsTF.setEditable(false);
324: gl3.insertLineBreakAfterNextComponent();
325: gl3.add(detailsTF);
326: detailsTF.setBackground(getBackground());
327:
328: remTimer.schedule(new TimerTask() {
329: public final void run() {
330: final StringBuilder sb = new StringBuilder();
331: try {
332: long dt = df.parse(dateTF.getText()).getTime()
333: - System.currentTimeMillis();
334: if (dt < 0) {
335: sb.append("Done "
336: + DateUtils
337: .formatTimeDifference(-dt)
338: + " ago");
339: } else {
340: sb.append("Will be done in "
341: + DateUtils
342: .formatTimeDifference(dt));
343: if (dt > 1000L * 3600L * 24L * 50) {
344: sb
345: .append(" ("
346: + ((int) Math
347: .round(dt
348: / (1000.0 * 3600 * 24)))
349: + " days)");
350: }
351: }
352:
353: dt = System.currentTimeMillis()
354: - df.parse(startDateTF.getText())
355: .getTime();
356: if (dt < 0) {
357: sb.append("\nCounter starts in "
358: + DateUtils
359: .formatTimeDifference(-dt)
360: + "");
361: if (-dt > 1000L * 3600L * 24L * 50) {
362: sb
363: .append(" ("
364: + ((int) Math
365: .round(-dt
366: / (1000.0 * 3600 * 24)))
367: + " days)");
368: }
369: } else {
370: sb.append("\nCounter started "
371: + DateUtils
372: .formatTimeDifference(dt)
373: + " ago");
374: if (dt > 1000L * 3600L * 24L * 50) {
375: sb
376: .append(" ("
377: + ((int) Math
378: .round(dt
379: / (1000.0 * 3600 * 24)))
380: + " days)");
381: }
382: }
383:
384: dt = df.parse(dateTF.getText()).getTime()
385: - df.parse(startDateTF.getText())
386: .getTime();
387:
388: sb.append("\nDuration: "
389: + DateUtils.formatTimeDifference(dt)
390: + "");
391: if (dt > 1000L * 3600L * 24L * 50) {
392: sb.append(" ("
393: + ((int) Math.round(dt
394: / (1000.0 * 3600 * 24)))
395: + " days)");
396: }
397:
398: } catch (Exception e) {
399: detailsTF.append("?");
400: }
401: detailsTF.setText(sb.toString());
402: }
403:
404: }, 1000L, 500L);
405:
406: pack();
407: this .setLocationRelativeTo(null);
408: setVisible(true); // MODAL
409:
410: remTimer.cancel();
411:
412: try {
413: cd.hasBeenAlerted = false; // REINITIALIZE !
414: getData(cd);
415: } catch (Exception e) {
416: JOptionPane.showMessageDialog(null, "Bad format", ""
417: + e.getMessage(), JOptionPane.ERROR_MESSAGE);
418: e.printStackTrace();
419: }
420: }
421:
422: private void setData(final CountdownData cd) {
423: nameTF.setText(cd.name);
424: dateTF.setText(df.format(cd.target));
425: startDateTF.setText(df.format(cd.created));
426: alertCB.setSelected(cd.shouldAlert);
427:
428: reccurenceCB.setSelectedIndex(cd.recurrence);
429: }
430:
431: private void getData(final CountdownData cd) throws Exception {
432: cd.name = nameTF.getText();
433: cd.target = df.parse(dateTF.getText()).getTime();
434: cd.created = df.parse(startDateTF.getText()).getTime();
435: cd.shouldAlert = alertCB.isSelected();
436: cd.recurrence = reccurenceCB.getSelectedIndex();
437: }
438: }
439:
440: public static long addOneMonth(long time) {
441: Calendar cal = GregorianCalendar.getInstance();
442: cal.setTimeInMillis(time);
443: cal.add(cal.MONTH, 1);
444: return cal.getTimeInMillis();
445: }
446:
447: public static long addOneYear(long time) {
448: Calendar cal = GregorianCalendar.getInstance();
449: cal.setTimeInMillis(time);
450: cal.add(cal.YEAR, 1);
451: return cal.getTimeInMillis();
452: }
453:
454: /** helper...
455: */
456: @SuppressWarnings("unchecked")
457: public static boolean lookIfActiveTimersArePresent() {
458: try {
459: if (!storageFile.exists())
460: return false;
461:
462: List<Object> reps = FileUtils
463: .loadVectorFromFile(storageFile);
464: for (Object ri : reps) {
465: CountdownData cd = new CountdownData();
466: cd.setFromRep((List<Object>) ri);
467:
468: if (cd.shouldAlert == true && !cd.hasBeenAlerted)
469: return true;
470: }
471:
472: return false;
473: } catch (Exception ex) {
474: JOptionPane.showMessageDialog(null, "Error", ""
475: + ex.getMessage(), JOptionPane.ERROR_MESSAGE);
476: ex.printStackTrace();
477: return false;
478: }
479: }
480:
481: @SuppressWarnings("unchecked")
482: private void load() {
483: try {
484: if (storageFile.exists()) {
485: List<Object> reps = FileUtils
486: .loadVectorFromFile(storageFile);
487: countdowns.clear();
488: for (Object ri : reps) {
489: CountdownData cd = new CountdownData();
490: cd.setFromRep((List<Object>) ri);
491: countdowns.add(cd);
492: }
493: }
494: } catch (Exception ex) {
495: JOptionPane.showMessageDialog(null, "Error", ""
496: + ex.getMessage(), JOptionPane.ERROR_MESSAGE);
497: ex.printStackTrace();
498: }
499: }
500:
501: /** Should be called on each changes.
502: */
503: @SuppressWarnings("unchecked")
504: private void save() {
505:
506: if (!storageFile.getParentFile().exists())
507: storageFile.getParentFile().mkdirs();
508: Vector rep = new Vector();
509: for (final CountdownData ci : countdowns) {
510: rep.add(ci.getRep());
511: }
512:
513: try {
514: FileUtils.saveVectorToFile(storageFile,
515: (Vector<Object>) rep);
516: } catch (Exception ex) {
517: JOptionPane.showMessageDialog(null, "Error", ""
518: + ex.getMessage(), JOptionPane.ERROR_MESSAGE);
519: ex.printStackTrace();
520: }
521:
522: installTrayIfNecessary();
523: }
524:
525: class Renderer extends DefaultTableCellRenderer {
526: final private JProgressBar pb = new JProgressBar(0, 100);
527:
528: public Component getTableCellRendererComponent(JTable table,
529: Object value, boolean isSelected, boolean hasFocus,
530: int row, int column) {
531: if (column == 3) {
532:
533: pb.setStringPainted(true);
534: CountdownData cd = (CountdownData) value;
535: pb.setString(cd.getStatusString());
536: int prog = (int) cd.getCompletion();
537: if (prog < 0)
538: prog = 0;
539: else if (prog > 100) {
540: //pb.setString("done.");
541: prog = 100;
542: }
543: pb.setValue(prog);
544: return pb;
545: }
546: Component c = super .getTableCellRendererComponent(table,
547: value, isSelected, hasFocus, row, column);
548: return c;
549: }
550: }
551:
552: class CountdownTable extends FineGrainTableModel {
553: int[] COLUMN_PREFERED_SIZES = new int[] { 16, 8, 8, 12 };
554: final private String[] COLUMN_NAMES = new String[] { "Name",
555: "Start", "End", "Status" };
556:
557: //private Vector<CountdownData> countdowns = new Vector<CountdownData>();
558:
559: public CountdownTable() {
560: }
561:
562: public Object getValueAt(int row, int column) {
563: CountdownData ci = countdowns.get(row);
564: if (column == 0)
565: return ci.name;
566: if (column == 1)
567: return df.format(ci.created);
568: if (column == 2)
569: return df.format(ci.target);
570: if (column == 3)
571: return ci;
572: return "";
573: }
574:
575: public int getRowCount() {
576: return countdowns.size();
577: }
578:
579: @SuppressWarnings("unchecked")
580: public int compareForColumnSort(int pos1, int pos2, int col) {
581: if (col == 0)
582: return super .compareForColumnSort(pos1, pos2, col);
583:
584: CountdownData c1 = countdowns.get(pos1);
585: CountdownData c2 = countdowns.get(pos2);
586: if (col == 1)
587: return Long.valueOf(c1.created).compareTo(c2.created);
588: if (col == 2)
589: return Long.valueOf(c1.target).compareTo(c2.target);
590: if (col == 3)
591: return Double.valueOf(c1.getCompletion()).compareTo(
592: c2.getCompletion());
593:
594: return super .compareForColumnSort(pos1, pos2, col);
595: }
596:
597: public void update() {
598: this .fireTableModelWillChange();
599: this .fireTableDataChanged();
600: this .fireTableModelHasChanged();
601: }
602:
603: @Override
604: public int getPreferredColumnWidth(int column) {
605: if (column >= 0 && column < COLUMN_PREFERED_SIZES.length)
606: return COLUMN_PREFERED_SIZES[column];
607: return -1;
608: }
609:
610: @Override
611: public String getColumnName(int column) {
612: if (column >= 0 && column < COLUMN_NAMES.length)
613: return COLUMN_NAMES[column];
614: return "Bad column " + column;
615: }
616:
617: public int getColumnCount() {
618: return COLUMN_NAMES.length;
619: }
620: }
621:
622: static class CountdownData implements Comparable<CountdownData> {
623:
624: public final int compareTo(final CountdownData o) {
625: return name.compareTo(o.name);
626: }
627:
628: long created = System.currentTimeMillis();
629: long target = System.currentTimeMillis() + 1000L * 3600L * 24
630: * 31; // one month
631: String name = "Countdown";
632: boolean shouldAlert = true;
633: boolean hasBeenAlerted = false;
634: String description = "";
635: int recurrence = 0;
636:
637: public void setHasBeenAlerted() {
638: hasBeenAlerted = true;
639: resetReccurence();
640: }
641:
642: public void resetReccurence() {
643: if (!hasBeenAlerted)
644: return;
645:
646: if (recurrence > 0) {
647:
648: if (recurrence == 1) // daily
649: {
650: while (target < System.currentTimeMillis()) {
651: target += 1000L * 3600L * 24;
652: }
653: } else if (recurrence == 2) // weekly
654: {
655: while (target < System.currentTimeMillis()) {
656: target += 1000L * 3600L * 24 * 7;
657: }
658: } else if (recurrence == 3) // monthly
659: {
660: while (target < System.currentTimeMillis()) {
661: target = addOneMonth(target);
662: }
663: } else if (recurrence == 4) // quarterly 3 months (1/4 of a year)
664: {
665: while (target < System.currentTimeMillis()) {
666: target = addOneMonth(addOneMonth(addOneMonth(target)));
667: }
668: } else if (recurrence == 5) // yearly
669: {
670: while (target < System.currentTimeMillis()) {
671: target = addOneYear(target);
672: }
673: } else if (recurrence == 6) {
674: while (target < System.currentTimeMillis()) {
675: target = target + 1000L * 3600;
676: }
677: }
678:
679: hasBeenAlerted = false;
680: }
681: }
682:
683: public StorageVector getRep() {
684: StorageVector rep = new StorageVector();
685: rep.add(1);
686: rep.add(name);
687: rep.add(created);
688: rep.add(target);
689: rep.add(shouldAlert);
690: rep.add(hasBeenAlerted);
691: rep.add(description);
692: rep.add(recurrence);
693: return rep;
694: }
695:
696: public void setFromRep(List<Object> rep) {
697: name = (String) rep.get(1);
698: created = (Long) rep.get(2);
699: target = (Long) rep.get(3);
700: shouldAlert = (Boolean) rep.get(4);
701: hasBeenAlerted = (Boolean) rep.get(5);
702: if (rep.size() > 6) {
703: description = (String) rep.get(6);
704: }
705: if (rep.size() > 7) {
706: recurrence = (Integer) rep.get(7);
707: }
708:
709: resetReccurence();
710: }
711:
712: /** When reached and not yet alerted.
713: */
714: public boolean shouldAlertNow() {
715: if (!shouldAlert)
716: return false;
717: if (hasBeenAlerted)
718: return false;
719:
720: double perc = getCompletion();
721: if (perc >= 100) {
722:
723: return true;
724: }
725:
726: return false;
727: }
728:
729: /** [0..100]
730: */
731: public double getCompletion() {
732: double perc = 100.0
733: * (System.currentTimeMillis() - created)
734: / (target - created);
735: return perc;
736: }
737:
738: public String getStatusString() {
739: double comp = getCompletion();
740: if (comp >= 100) {
741: long dt = System.currentTimeMillis() - target;
742: return "done. (" + DateUtils.formatTimeDifference(dt)
743: + " ago)";
744: } else {
745: long dt = System.currentTimeMillis() - created;
746: if (dt < 0) {
747: return "not yet started. (in "
748: + DateUtils.formatTimeDifference(-dt) + ")";
749: } else {
750: dt = target - System.currentTimeMillis();
751: return "in " + DateUtils.formatTimeDifference(dt);
752: }
753: }
754: /*
755: long dt = df.parse(dateTF.getText()).getTime() - System.currentTimeMillis();
756: if(dt<0)
757: {
758: remTF.setText(DateUtils.formatTimeDifference(-dt)+" ago");
759: }
760: else
761: {
762: remTF.setText("In "+DateUtils.formatTimeDifference(dt));
763: } */
764: // return "?";
765: }
766:
767: }
768:
769: public static void main(String[] args) {
770: Countdown.standalone = true;
771: Countdown.getInstance().setVisible(true);
772: }
773:
774: }
|