001: /*
002: This file is part of BORG.
003:
004: BORG is free software; you can redistribute it and/or modify
005: it under the terms of the GNU General Public License as published by
006: the Free Software Foundation; either version 2 of the License, or
007: (at your option) any later version.
008:
009: BORG is distributed in the hope that it will be useful,
010: but WITHOUT ANY WARRANTY; without even the implied warranty of
011: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
012: GNU General Public License for more details.
013:
014: You should have received a copy of the GNU General Public License
015: along with BORG; if not, write to the Free Software
016: Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
017:
018: Copyright 2003 by Mike Berger
019: */
020: /*
021: * popups.java
022: *
023: * Created on January 16, 2004, 3:08 PM
024: */
025:
026: package net.sf.borg.ui.popup;
027:
028: import java.applet.Applet;
029: import java.applet.AudioClip;
030: import java.awt.EventQueue;
031: import java.net.URL;
032: import java.text.SimpleDateFormat;
033: import java.util.ArrayList;
034: import java.util.Calendar;
035: import java.util.Collection;
036: import java.util.Date;
037: import java.util.GregorianCalendar;
038: import java.util.HashMap;
039: import java.util.Iterator;
040: import java.util.Set;
041: import java.util.TimerTask;
042: import java.util.Map.Entry;
043:
044: import net.sf.borg.common.Errmsg;
045: import net.sf.borg.common.PrefName;
046: import net.sf.borg.common.Prefs;
047: import net.sf.borg.common.Resource;
048: import net.sf.borg.model.AppointmentModel;
049: import net.sf.borg.model.ReminderTimes;
050: import net.sf.borg.model.beans.Appointment;
051: import net.sf.borg.ui.View;
052:
053: /**
054: *
055: * @author mberger
056: */
057: public class PopupView extends View {
058:
059: // map that maps appointment keys to the associated popup reminder
060: // windows
061: private HashMap pops = new HashMap();
062:
063: private java.util.Timer timer = null;
064:
065: final Runnable doPopupChk = new Runnable() {
066: public void run() {
067: popup_chk();
068: }
069: };
070:
071: /** Creates a new instance of popups */
072: public PopupView() {
073: addModel(AppointmentModel.getReference());
074: timer = new java.util.Timer();
075: // start popups at next minute on system clock
076: GregorianCalendar cur = new GregorianCalendar();
077: int secs_left = 60 - cur.get(Calendar.SECOND);
078: timer.schedule(new TimerTask() {
079: public void run() {
080: EventQueue.invokeLater(doPopupChk);
081: }
082: }, secs_left * 1000, Prefs
083: .getIntPref(PrefName.REMINDERCHECKMINS) * 60 * 1000);
084:
085: }
086:
087: public void destroy() {
088:
089: timer.cancel();
090:
091: // get rid of any open popups
092: Collection s = pops.values();
093: Iterator i = s.iterator();
094: while (i.hasNext()) {
095:
096: ReminderPopup pop = (ReminderPopup) i.next();
097:
098: // if frame is gone (killed already), then skip it
099: if (pop == null)
100: continue;
101: pop.dispose();
102:
103: }
104: }
105:
106: // find any popups that should no longer be displayed based on updates to the DB and delete them
107: public void refresh() {
108: ArrayList dels = new ArrayList();
109: Set s = pops.entrySet();
110: Iterator i = s.iterator();
111: while (i.hasNext()) {
112: // get popup frame
113: Entry me = (Entry) i.next();
114: Integer apptkey = (Integer) me.getKey();
115: ReminderPopup fr = (ReminderPopup) me.getValue();
116:
117: // if frame is gone (killed already), then skip it
118: if (fr == null)
119: continue;
120:
121: // skip if popup not being shown - but still in map
122: if (!fr.isDisplayable()) {
123: // free resources from JFrame and remove from map
124: // map should be last reference to the frame so garbage
125: // collection should now be free to clean it up
126: me.setValue(null);
127: continue;
128: }
129:
130: try {
131: //System.out.println("checking popup for appt: " + apptkey);
132: // read the appt and get the date
133: Appointment appt = AppointmentModel.getReference()
134: .getAppt(apptkey.intValue());
135: if (!shouldBeShown(appt)) {
136: fr.dispose();
137: dels.add(apptkey);
138: //System.out.println("disposing popup for appt: " + apptkey);
139: }
140: } catch (Exception e) {
141: // appt cannot be read - kill the popup
142: dels.add(apptkey);
143: fr.dispose();
144: //System.out.println("disposing popup for appt: " + apptkey);
145: }
146: }
147:
148: Iterator it = dels.iterator();
149: while (it.hasNext()) {
150: pops.remove(it.next());
151: }
152:
153: }
154:
155: // If the reminder should be shown, return the "minutes before
156: // appointment"
157: // value
158: // of the requested reminder, so that we can display this in the
159: // reminder
160: // If it should not be shown, return -999
161: private int due_for_popup(long mins_to_go, Appointment appt,
162: ReminderPopup p) {
163: char[] remTimes = new char[ReminderTimes.getNum()];
164: try {
165: remTimes = (appt.getReminderTimes()).toCharArray();
166:
167: } catch (Exception e) {
168: for (int i = 0; i < ReminderTimes.getNum(); ++i) {
169: remTimes[i] = 'N';
170: }
171: }
172:
173: int i = 0;
174: while (ReminderTimes.getTimes(i) < mins_to_go) {
175: ++i;
176: }
177:
178: // shouldnt happen
179: if (i >= ReminderTimes.getNum()) {
180: return -999;
181: }
182:
183: if (remTimes[i] == 'Y' && p.reminderShown(i) == 'N') {
184: p.setReminderShown(i);
185: return ReminderTimes.getTimes(i);
186: }
187:
188: return -999;
189:
190: }
191:
192: static private boolean outside_reminder_times(long mins_to_go,
193: Appointment appt) {
194:
195: int earliest = -999;
196: char[] remTimes = new char[ReminderTimes.getNum()];
197: try {
198: remTimes = (appt.getReminderTimes()).toCharArray();
199:
200: } catch (Exception e) {
201: for (int i = 0; i < ReminderTimes.getNum(); ++i) {
202: remTimes[i] = 'N';
203: }
204: }
205:
206: for (int i = 0; i < ReminderTimes.getNum(); i++) {
207: if (remTimes[i] == 'Y')
208: earliest = ReminderTimes.getTimes(i);
209: }
210:
211: // System.out.println("mtg =" + mins_to_go + " rt=" +
212: // appt.getReminderTimes() + " l=" + latest + " e=" + earliest);
213: if (earliest == -999)
214: return true;
215:
216: return (mins_to_go > earliest || mins_to_go < ReminderTimes
217: .getTimes(0));
218: }
219:
220: private static boolean shouldBeShown(Appointment appt) {
221:
222: if (appt == null || appt.getDeleted())
223: return false;
224:
225: // check if we should show it based on public/private
226: // flags
227:
228: boolean showpub = false;
229: boolean showpriv = false;
230: String sp = Prefs.getPref(PrefName.SHOWPUBLIC);
231: if (sp.equals("true"))
232: showpub = true;
233: sp = Prefs.getPref(PrefName.SHOWPRIVATE);
234: if (sp.equals("true"))
235: showpriv = true;
236: if (appt.getPrivate()) {
237: if (!showpriv)
238: return false;
239: } else {
240: if (!showpub)
241: return false;
242: }
243:
244: // don't popup "notes"
245: if (AppointmentModel.isNote(appt) && !appt.getTodo())
246: return false;
247:
248: if (AppointmentModel.isNote(appt) && appt.getTodo()
249: && appt.getReminderTimes() != null
250: && appt.getReminderTimes().indexOf('Y') != -1) {
251: // non-timed todo
252:
253: // make sure todo is not done for today
254: GregorianCalendar td = new GregorianCalendar();
255: td.set(Calendar.HOUR_OF_DAY, 23);
256: td.set(Calendar.MINUTE, 59);
257:
258: Date nt = appt.getNextTodo();
259: if (nt != null && nt.after(td.getTime())) {
260: return false;
261: }
262: } else {
263: Date d = appt.getDate();
264:
265: // set appt time for computation
266: GregorianCalendar now = new GregorianCalendar();
267: GregorianCalendar acal = new GregorianCalendar();
268: acal.setTime(d);
269:
270: // need to set appt time to today in case it is a
271: // repeating
272: // appt. if it is a repeat,
273: // the time will be right, but the day will be the day
274: // of
275: // the first repeat
276: acal.set(now.get(Calendar.YEAR), now.get(Calendar.MONTH),
277: now.get(Calendar.DATE));
278:
279: // skip the appt if it is outside the time frame of the
280: // reminder requests
281: long mins_to_go = acal.getTimeInMillis() / (1000 * 60)
282: - now.getTimeInMillis() / (1000 * 60);
283:
284: if (outside_reminder_times(mins_to_go, appt))
285: return false;
286: }
287:
288: return true;
289: }
290:
291: // check if any new popup windows are needed and pop them up
292: // also beep and bring imminent popups to the front
293: private void popup_chk() {
294:
295: String enable = Prefs.getPref(PrefName.REMINDERS);
296: if (enable.equals("false"))
297: return;
298:
299: // get the key for today in the data model
300: int key = AppointmentModel.dkey(new GregorianCalendar());
301:
302: // get the list of the today's appts
303: Collection l = AppointmentModel.getReference().getAppts(key);
304: if (l != null) {
305: Iterator it = l.iterator();
306: Appointment appt;
307:
308: // iterate through the day's appts
309: while (it.hasNext()) {
310:
311: Integer ik = (Integer) it.next();
312:
313: try {
314: // read the appt record from the data model
315: appt = AppointmentModel.getReference().getAppt(
316: ik.intValue());
317:
318: if (!shouldBeShown(appt))
319: continue;
320:
321: // skip appt if it is already in the pops list
322: // this means that it is already showing - or was shown
323: // and
324: // killed already
325: if (pops.containsKey(ik))
326: continue;
327:
328: // create a new frame for a popup and add it to the
329: // popup
330: // map
331: // along with the appt key
332:
333: ReminderPopup jd = new ReminderPopup();
334:
335: pops.put(ik, jd);
336:
337: // add text to date
338: String tx = "";
339: if (!AppointmentModel.isNote(appt)) {
340: SimpleDateFormat df = AppointmentModel
341: .getTimeFormat();
342: tx = df.format(appt.getDate());
343: }
344:
345: tx += " " + appt.getText();
346: jd.setText(tx);
347: jd.setText2("");
348: jd.setVisible(true);
349: jd.toFront();
350:
351: } catch (Exception e) {
352: Errmsg.errmsg(e);
353: }
354: }
355: }
356:
357: // if any popups that are already displayed are due for showing - make a
358: // sound
359: // and raise the popup
360:
361: // iterate through existing popups
362: String enablebeep = Prefs.getPref(PrefName.BEEPINGREMINDERS);
363: if (enablebeep.equals("false")) {
364: return;
365: }
366:
367: Set s = pops.entrySet();
368: Iterator i = s.iterator();
369: while (i.hasNext()) {
370: // get popup frame
371: Entry me = (Entry) i.next();
372: Integer apptkey = (Integer) me.getKey();
373: ReminderPopup fr = (ReminderPopup) me.getValue();
374:
375: // if frame is gone (killed already), then skip it
376: if (fr == null)
377: continue;
378:
379: // skip if popup not being shown - but still in map
380: if (!fr.isDisplayable()) {
381: // free resources from JFrame and remove from map
382: // map should be last reference to the frame so garbage
383: // collection should now be free to clean it up
384: me.setValue(null);
385: continue;
386: }
387:
388: try {
389: // read the appt and get the date
390: Appointment appt = AppointmentModel.getReference()
391: .getAppt(apptkey.intValue());
392:
393: String time_msg;
394:
395: if (AppointmentModel.isNote(appt) && appt.getTodo()) {
396:
397: // show on the half hour or on startup
398: int min = new GregorianCalendar()
399: .get(Calendar.MINUTE);
400: if (fr.wasShown() && !(min == 0 || min == 30))
401: continue;
402:
403: time_msg = Resource.getPlainResourceString("To_Do")
404: + " "
405: + Resource.getPlainResourceString("Today");
406: } else {
407: Date d = appt.getDate();
408: if (d == null)
409: continue;
410:
411: // determine how far away the appt is
412: GregorianCalendar acal = new GregorianCalendar();
413: acal.setTime(d);
414: GregorianCalendar now = new GregorianCalendar();
415:
416: // need to set appt time to today in case it is a
417: // repeating
418: // appt. if it is a repeat,
419: // the time will be right, but the day will be the day
420: // of
421: // the
422: // first repeat
423: acal.set(now.get(Calendar.YEAR), now
424: .get(Calendar.MONTH), now
425: .get(Calendar.DATE));
426:
427: long mins_to_go = acal.getTimeInMillis()
428: / (1000 * 60) - now.getTimeInMillis()
429: / (1000 * 60);
430:
431: // if alarm is due to be shown, show it and play sound
432: // if
433: // requested
434: int alarmid = due_for_popup(mins_to_go, appt, fr);
435: if (alarmid == -999)
436: continue;
437:
438: if (alarmid < 0) {
439: time_msg = -alarmid
440: + " "
441: + Resource
442: .getResourceString("minutes_ago");
443: } else if (alarmid == 0) {
444: time_msg = Resource.getResourceString("Now");
445: } else {
446: time_msg = alarmid
447: + " "
448: + Resource
449: .getResourceString("minute_reminder");
450: }
451: }
452:
453: fr.setText2(time_msg);
454: fr.setVisible(true);
455: fr.toFront();
456: fr.setVisible(true);
457: fr.setShown(true);
458:
459: // play sound
460: if (Prefs.getPref(PrefName.USESYSTEMBEEP)
461: .equals("true")) {
462: java.awt.Toolkit.getDefaultToolkit().beep();
463: } else {
464: URL snd = getClass().getResource(
465: "/resource/blip.wav");
466: AudioClip theSound;
467:
468: theSound = Applet.newAudioClip(snd);
469:
470: if (theSound != null) {
471: theSound.play();
472: }
473: }
474:
475: } catch (Exception e) {
476: // ignore errors here
477: }
478: }
479: }
480: }
|