Mercurial > hg > xemacs-beta
comparison lisp/calendar/appt.el @ 0:376386a54a3c r19-14
Import from CVS: tag r19-14
author | cvs |
---|---|
date | Mon, 13 Aug 2007 08:45:50 +0200 |
parents | |
children | 0293115a14e9 |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:376386a54a3c |
---|---|
1 ;;; appt.el --- appointment notification functions. | |
2 ;; Keywords: calendar | |
3 | |
4 ;;; -*- Mode:Emacs-Lisp -*- | |
5 ;; Appointment notification functions. | |
6 ;; Copyright (C) 1989, 1990, 1992, 1993, 1994 Free Software Foundation, Inc. | |
7 | |
8 ;; This file is part of XEmacs. | |
9 | |
10 ;; XEmacs is free software; you can redistribute it and/or modify it | |
11 ;; under the terms of the GNU General Public License as published by | |
12 ;; the Free Software Foundation; either version 2, or (at your option) | |
13 ;; any later version. | |
14 | |
15 ;; XEmacs is distributed in the hope that it will be useful, but | |
16 ;; WITHOUT ANY WARRANTY; without even the implied warranty of | |
17 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
18 ;; General Public License for more details. | |
19 | |
20 ;; You should have received a copy of the GNU General Public License | |
21 ;; along with XEmacs; see the file COPYING. If not, write to the Free | |
22 ;; Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. | |
23 | |
24 ;;; 29-nov-89 created by Neil Mager <neilm@juliet.ll.mit.edu>. | |
25 ;;; 23-feb-91 hacked upon by Jamie Zawinski <jwz@lucid.com>. | |
26 ;;; 1-apr-91 some more. | |
27 ;;; 12-jul-95 updated for XEmacs 19.12 by Greg Veres <gveres@cgl.uwaterloo.ca> | |
28 ;;; | |
29 ;; appt.el - visible and/or audible notification of | |
30 ;; appointments from ~/diary file generated from | |
31 ;; Edward M. Reingold's calendar.el. | |
32 ;; | |
33 ;; Version 2.1 | |
34 ;; | |
35 ;; Comments, corrections, and improvements should be sent to | |
36 ;; Neil M. Mager | |
37 ;; Net <neilm@juliet.ll.mit.edu> | |
38 ;; Voice (617) 981-4803 | |
39 ;;; | |
40 ;;; Thanks to Edward M. Reingold for much help and many suggestions, | |
41 ;;; And to many others for bug fixes and suggestions. | |
42 ;;; | |
43 ;;; | |
44 ;;; This functions in this file will alert the user of a | |
45 ;;; pending appointment based on their diary file. | |
46 ;;; | |
47 ;;; ******* It is necessary to invoke 'display-time' and ******** | |
48 ;;; ******* 'appt-initialize' for this to work properly. ******** | |
49 ;;; | |
50 ;;; A message will be displayed in the mode line of the emacs buffer and (if | |
51 ;;; the user desires) the terminal will beep and display a message from the | |
52 ;;; diary in the mini-buffer, or the user may select to have a message | |
53 ;;; displayed in a new buffer. | |
54 ;;; | |
55 ;;; Variables of note: | |
56 ;;; | |
57 ;;; appt-issue-message If this variable is nil, then the code in this | |
58 ;;; file does nothing. | |
59 ;;; appt-msg-countdown-list Specifies how much warning you want before | |
60 ;;; appointments. | |
61 ;;; appt-audible Whether to beep when it's notification-time. | |
62 ;;; appt-display-mode-line Whether to display a countdown to the next | |
63 ;;; appointment in the mode-line. | |
64 ;;; appt-announce-method The function used to do the notifications. | |
65 ;;; 'appt-window-announce do it in a pop-up window. | |
66 ;;; 'appt-frame-announce do it in a pop-up frame (v19 only) | |
67 ;;; 'appt-message-announce do it in the echo area. | |
68 ;;; 'appt-persistent-message-announce do it in the echo area, but make the | |
69 ;;; messages not go away at the next keystroke. | |
70 ;;; appt-display-duration If appt-announce-method is set to the function | |
71 ;;; 'appt-window-announce, this specifies how many | |
72 ;;; seconds the pop-up window should stick around. | |
73 ;;; | |
74 ;;; In order to use this, create a diary file, and add the following to your | |
75 ;;; .emacs file: | |
76 ;;; | |
77 ;;; (require 'appt) | |
78 ;;; (display-time) | |
79 ;;; (appt-initialize) | |
80 ;;; | |
81 ;;; If you wish to see a list of appointments, or a full calendar, when emacs | |
82 ;;; starts up, you can add a call to (diary) or (calendar) after this. | |
83 ;;; | |
84 ;;; This is an example of what can be in your diary file: | |
85 ;;; Monday | |
86 ;;; 9:30am Coffee break | |
87 ;;; 12:00pm Lunch | |
88 ;;; | |
89 ;;; Based upon the above lines in your .emacs and diary files, the calendar | |
90 ;;; and/or diary will be displayed when you enter emacs and your appointments | |
91 ;;; list will automatically be created. You will then be reminded at 9:20am | |
92 ;;; about your coffee break and at 11:50am to go to lunch. | |
93 ;;; | |
94 ;;; In order to interactively add or delete items from today's list, use | |
95 ;;; Meta-x appt-add and Meta-x appt-delete. (This does not modify your | |
96 ;;; diary file, so these will be forgotten when you exit emacs.) | |
97 ;;; | |
98 ;;; Additionally, the appointments list is recreated automatically at 12:01am | |
99 ;;; for those who do not logout every day or are programming late. | |
100 ;;; | |
101 ;;; You can have special appointments which execute arbitrary code rather than | |
102 ;;; simply notifying you -- sort of like the unix "cron" facility. The syntax | |
103 ;;; for this is borrowed from the Calendar's special-date format. If you have | |
104 ;;; a diary entry like | |
105 ;;; | |
106 ;;; Monday | |
107 ;;; 3:00am %%(save-all-modified-buffers) | |
108 ;;; | |
109 ;;; then on monday at 3AM, the function `save-all-modified-buffers' will be | |
110 ;;; invoked. (Presumably this function is defined in your .emacs file.) | |
111 ;;; There will be no notification that these "special" appointments are being | |
112 ;;; triggered, unless the form evaluated produces a notification. | |
113 ;;; | |
114 ;;; It is necessary for the entire list after the "%%" to be on one line in | |
115 ;;; your .diary file -- there may not be embedded newlines in it. This is a | |
116 ;;; bit of a misfeature. | |
117 ;;; | |
118 ;;; This also interacts correctly with Benjamin Pierce's reportmail.el package. | |
119 ;;; | |
120 ;;; Brief internal description - Skip this if your not interested! | |
121 ;;; | |
122 ;;; The function appt-initialize invokes 'diary' to get a list of today's | |
123 ;;; appointments, and parses the lines beginning with date descriptions. | |
124 ;;; This list is cached away. 'diary' is invoked in such a way so as to | |
125 ;;; not pop up a window displaying the diary buffer. | |
126 ;;; | |
127 ;;; The function appt-check is run from the 'loadst' process (or the 'wakeup' | |
128 ;;; process in emacs 18.57 or newer) which is started by invoking display-time. | |
129 ;;; It checks this cached list, and announces as appropriate. At midnight, | |
130 ;;; appt-initialize is called again to rebuild this list. | |
131 ;;; | |
132 ;;; display-time-filter is modified to invoke appt-check. | |
133 ;;; | |
134 ;;; TO DO: | |
135 ;;; | |
136 ;;; o multiple adjascent appointments are not handled gracefully. If there | |
137 ;;; is an appointment at 3:30 and another at 3:35, and you have set things | |
138 ;;; up so that you get a notification twenty minutes before each appt, | |
139 ;;; then a notification should come at 3:10 for the first appt, and at | |
140 ;;; 3:15 for the second. Currently, no notifications are generated for an | |
141 ;;; appointment until all preceeding appointments have completely expired. | |
142 ;;; | |
143 ;;; o If there are two appointments at the same time, all but the first are | |
144 ;;; ignored (not announced.) | |
145 ;;; | |
146 ;;; o Appointments which are early enough in the morning that their | |
147 ;;; announcements should begin before midnight are not announced until | |
148 ;;; midnight. | |
149 ;;; | |
150 ;;; o There should be some way to mark certain appointments as "important," | |
151 ;;; so that you will be harassed about them even after they have expired. | |
152 | |
153 | |
154 (require 'calendar) | |
155 (require 'diary-lib) | |
156 | |
157 (defvar appt-issue-message t | |
158 "*If T, the diary buffer is checked for appointments. For an | |
159 appointment warning to be made, the time must be the first thing on | |
160 the line.") | |
161 | |
162 (defvar appt-msg-countdown-list '(20 15 10 5 3 1) | |
163 "*A list of the intervals in minutes before the appointment when | |
164 the warnings will be given. That is, if this were the list '(5 3 1), | |
165 then a notification would be given five minutes, three minutes, and | |
166 one minute before the appointment.") | |
167 | |
168 (defvar appt-check-time-syntax nil | |
169 "*Whether all diary entries are intended to beging with time specifications. | |
170 Appt will beep and issue a warning message when encountering unparsable | |
171 lines.") | |
172 | |
173 (defvar appt-audible t | |
174 "*Controls whether appointment announcements should beep. | |
175 Appt uses two sound-types for beeps: `appt' and `appt-final'. | |
176 If this is a number, then that many beeps will occur. | |
177 If this is a cons, the car is how many beeps, and the cdr is the | |
178 delay between them (a float, fraction of a second to sleep.) | |
179 See also the variable `appt-msg-countdown-list'") | |
180 | |
181 (defvar appt-display-mode-line t | |
182 "*Controls if minutes-to-appointment should be displayed on the mode line.") | |
183 | |
184 (defvar appt-announce-method 'appt-window-announce | |
185 "*The name of the function used to notify the user of an impending | |
186 appointment. This is called with two arguments, the number of minutes | |
187 until the appointment, and the appointment description list. | |
188 | |
189 Reasonable values for this variable are 'appt-window-announce, | |
190 'appt-message-announce, or 'appt-persistent-message-announce.") | |
191 | |
192 | |
193 (defvar appt-time-msg-list nil | |
194 "The list of appointments for today. Use appt-add and appt-delete | |
195 to add and delete appointments from list. The original list is generated | |
196 from the today's diary-entries-list. The number before each time/message | |
197 is the time in minutes after midnight.") | |
198 | |
199 (defconst max-time 1439 | |
200 "11:59pm in minutes - number of minutes in a day minus 1.") | |
201 | |
202 (defconst appt-check-tick -1) | |
203 | |
204 (defvar appt-disp-frame nil | |
205 "If non-nil, frame to display appointments in.") | |
206 (defvaralias 'appt-disp-screen 'appt-disp-frame) | |
207 | |
208 | |
209 ;;; Announcement methods | |
210 | |
211 (defun appt-message-announce (min-to-app appt) | |
212 "Set appt-announce-method to the name of this function to cause appointment | |
213 notifications to be given via messages in the minibuffer." | |
214 (message (if (eq min-to-app 0) "App't NOW." | |
215 (format "App't in %d minute%s -- %s" | |
216 min-to-app | |
217 (if (eq 1 min-to-app) "" "s") | |
218 (car (cdr appt)))))) | |
219 | |
220 | |
221 (defun appt-persistent-message-announce (min-to-app appt) | |
222 "Set appt-announce-method to the name of this function to cause appointment | |
223 notifications to be given via messages in the minibuffer, but have those | |
224 messages stay around even if you type something (unlike normal messages)." | |
225 (let ((str (if (eq min-to-app 0) | |
226 (format "App't NOW -- %s" (car (cdr appt))) | |
227 (format "App't in %d minute%s -- %s" | |
228 min-to-app | |
229 (if (eq 1 min-to-app) "" "s") | |
230 (car (cdr appt))))) | |
231 (in-echo-area-already (eq (selected-window) (minibuffer-window)))) | |
232 (if (not in-echo-area-already) | |
233 ;; don't stomp the echo-area-buffer if reading from the minibuffer now. | |
234 (save-excursion | |
235 (save-window-excursion | |
236 (select-window (minibuffer-window)) | |
237 (delete-region (point-min) (point-max)) | |
238 (insert str)))) | |
239 ;; if we're reading from the echo-area, and all we were going to do is | |
240 ;; clear the thing, like, don't bother, that's annoying. | |
241 (if (and in-echo-area-already (string= "" str)) | |
242 nil | |
243 (message "%s" str)) | |
244 )) | |
245 | |
246 | |
247 (defvar appt-display-duration 5 | |
248 "*The number of seconds an appointment message is displayed in its own | |
249 window if appt-announce-method is 'appt-window-announce.") | |
250 | |
251 (defun appt-window-announce (min-to-app appt) | |
252 "Set appt-announce-method to the name of this function to cause appointment | |
253 notifications to be given via messages in a pop-up window. The variable | |
254 appt-display-duration controls how long this window should be left up." | |
255 (require 'electric) | |
256 (save-excursion | |
257 (save-window-excursion | |
258 ;; Make sure we're not in the minibuffer | |
259 ;; before splitting the window. | |
260 (if (window-minibuffer-p (selected-window)) | |
261 nil | |
262 (select-window (frame-lowest-window)) | |
263 (split-window)) | |
264 (let (appt-disp-buf) | |
265 (unwind-protect | |
266 (progn | |
267 (setq appt-disp-buf (set-buffer (get-buffer-create "*appt-buf*"))) | |
268 ;; set the mode-line of the pop-up window | |
269 (setq modeline-format | |
270 (concat "-------------------- Appointment " | |
271 (if (eq min-to-app 0) | |
272 "NOW" | |
273 (concat "in " min-to-app | |
274 (if (eq min-to-app 1) " minute" " minutes"))) | |
275 ". (" | |
276 (let ((h (string-to-int | |
277 (substring (current-time-string) 11 13)))) | |
278 (concat (if (> h 12) (- h 12) h) ":" | |
279 (substring (current-time-string) 14 16) | |
280 (if (< h 12) "am" "pm"))) | |
281 ") %-")) | |
282 (pop-to-buffer appt-disp-buf) | |
283 (insert (car (cdr appt))) | |
284 (shrink-window-if-larger-than-buffer | |
285 (get-buffer-window appt-disp-buf)) | |
286 (set-buffer-modified-p nil) | |
287 (sit-for appt-display-duration)) | |
288 (and appt-disp-buf (kill-buffer appt-disp-buf))))))) | |
289 | |
290 (defvar appt-frame-defaults nil) | |
291 (defvaralias 'appt-screen-defaults 'appt-frame-defaults) | |
292 | |
293 (defun appt-frame-announce (min-to-app appt) | |
294 "Set appt-announce-method to the name of this function to cause appointment | |
295 notifications to be given via messages in a pop-up frame." | |
296 (let () | |
297 (save-excursion | |
298 (set-buffer (get-buffer-create "*appt-buf*")) | |
299 (erase-buffer) | |
300 ;; set the mode-line of the pop-up window | |
301 (setq modeline-format | |
302 (concat "-------------------- Appointment " | |
303 (if (eq min-to-app 0) | |
304 "NOW" | |
305 (concat "in " min-to-app | |
306 (if (eq min-to-app 1) " minute" " minutes"))) | |
307 ". (" | |
308 (let ((h (string-to-int | |
309 (substring (current-time-string) 11 13)))) | |
310 (concat (if (> h 12) (- h 12) h) ":" | |
311 (substring (current-time-string) 14 16) | |
312 (if (< h 12) "am" "pm"))) | |
313 ") %-")) | |
314 (insert (car (cdr appt))) | |
315 (let ((height (max 10 (min 20 (+ 2 (count-lines (point-min) | |
316 (point-max))))))) | |
317 ;; If we already have a frame constructed, use it. If not, or it has | |
318 ;; been deleted, then make a new one | |
319 (if (and appt-disp-frame (frame-live-p appt-disp-frame)) | |
320 (let ((s (selected-frame))) | |
321 (select-frame appt-disp-frame) | |
322 (make-frame-visible appt-disp-frame) | |
323 (set-frame-height appt-disp-frame height) | |
324 (sit-for 0) | |
325 (select-frame s)) | |
326 (progn | |
327 (setq appt-disp-frame (make-frame)) | |
328 (set-frame-height appt-disp-frame height) | |
329 ) | |
330 ) | |
331 ) | |
332 ) | |
333 ) | |
334 ) | |
335 (defalias 'appt-screen-announce 'appt-frame-announce) | |
336 | |
337 ;;; To display stuff in the mode line, we use a new variable instead of | |
338 ;;; just adding stuff to the display-time-string -- this causes less | |
339 ;;; flicker. | |
340 | |
341 (defvar appt-mode-line-string "" | |
342 "*The string displayed in the mode line by the appointment package.") | |
343 | |
344 (defun appt-display-mode-line (min-to-app) | |
345 "Add an appointment annotation to the mode line." | |
346 (setq appt-mode-line-string | |
347 (if (and appt-display-mode-line min-to-app) | |
348 (if (eq 0 min-to-app) | |
349 "App't NOW " | |
350 (concat "App't in " min-to-app | |
351 (if (eq 1 min-to-app) " minute " " minutes "))) | |
352 "")) | |
353 ;; make sure our variable is visible in global-mode-string. | |
354 (cond ((not appt-display-mode-line) nil) | |
355 ((null global-mode-string) | |
356 (setq global-mode-string (list "" 'appt-mode-line-string))) | |
357 ((stringp global-mode-string) | |
358 (setq global-mode-string | |
359 (list global-mode-string 'appt-mode-line-string))) | |
360 ((not (memq 'appt-mode-line-string global-mode-string)) | |
361 (setq global-mode-string | |
362 (append global-mode-string (list 'appt-mode-line-string))))) | |
363 ;; force mode line updates - from time.el | |
364 (save-excursion (set-buffer (other-buffer))) | |
365 (set-buffer-modified-p (buffer-modified-p)) | |
366 (sit-for 0)) | |
367 | |
368 | |
369 ;;; Internal stuff | |
370 | |
371 (defun appt-convert-time (time2conv) | |
372 " Convert hour:min[am/pm] format to minutes from midnight." | |
373 (cond ((string-match "^[ \t]*midni\\(ght\\|te\\)[ \t]*\\'" time2conv) | |
374 0) | |
375 ((string-match "^[ \t]*noon[ \t]*\\'" time2conv) | |
376 (* 12 60)) | |
377 (t | |
378 (let ((hr 0) | |
379 (min 0)) | |
380 (or (string-match | |
381 "\\`[ \t]*\\([0-9][0-9]?\\)[ \t]*\\(:[ \t]*\\([0-9][0-9]\\)\\)?[ \t]*\\(am\\|pm\\)?" | |
382 time2conv) | |
383 (error "unparsable time \"%s\"" time2conv)) | |
384 (setq hr (string-to-int | |
385 (substring time2conv | |
386 (match-beginning 1) (match-end 1)))) | |
387 (if (match-beginning 3) | |
388 (setq min (string-to-int | |
389 (substring time2conv | |
390 (match-beginning 3) (match-end 3))))) | |
391 ;; convert the time appointment time into 24 hour time | |
392 (if (match-beginning 4) | |
393 (progn | |
394 (if (or (= hr 0) (> hr 12)) | |
395 (error "mixing 12hr and 24 hr time! %s" time2conv)) | |
396 (if (string-match "am" | |
397 (substring time2conv (match-beginning 4))) | |
398 (if (= hr 12) (setq hr 0)) | |
399 (if (< hr 12) (setq hr (+ 12 hr)))))) | |
400 (if (> min 59) (error "minutes outa bounds - %s" time2conv)) | |
401 (+ (* hr 60) min))))) | |
402 | |
403 | |
404 (defun appt-current-time-in-seconds () | |
405 "returns the current time in seconds since midnight." | |
406 (let* ((str (current-time-string)) | |
407 (hour (string-to-int (substring str 11 13))) | |
408 (min (string-to-int (substring str 14 16)))) | |
409 (+ (* hour 60) min))) | |
410 | |
411 | |
412 (defun appt-sort-list (appt-list) | |
413 (sort (copy-sequence appt-list) | |
414 (function (lambda (x y) | |
415 (< (car (car x)) (car (car y))))))) | |
416 | |
417 (defun appt-diary-entries () | |
418 (let ((list-diary-entries-hook '(appt-make-list)) | |
419 (diary-display-hook nil) | |
420 (diary-list-include-blanks nil)) | |
421 ;; this will set appt-time-msg-list. | |
422 (diary 1) | |
423 appt-time-msg-list)) | |
424 | |
425 (defun appt-initialize () | |
426 " Read your `diary-file' and remember today's appointments. Call this from | |
427 your .emacs file, or any time you want your .diary file re-read (this happens | |
428 automatically at midnight to move to notice the next day's appointments). | |
429 | |
430 The time must be at the beginning of a line for it to be put in the | |
431 appointments list. | |
432 02/23/89 | |
433 12:00pm lunch | |
434 Wednesday | |
435 10:00am group meeting" | |
436 (install-display-time-hook) | |
437 (let ((n (length (appt-diary-entries)))) | |
438 (cond ((= n 0) (message "no appointments today.")) | |
439 ((= n 1) (message "1 appointment today.")) | |
440 (t (message (format "%d appointments today." n)))))) | |
441 | |
442 (defun appt-make-list () | |
443 "Don't call this directly; call appt-initialize or appt-diary-entries." | |
444 (setq appt-time-msg-list nil) | |
445 (if diary-entries-list | |
446 ;; Cycle through the entry-list (diary-entries-list) looking for | |
447 ;; entries beginning with a time. If the entry begins with a time, | |
448 ;; add it to the appt-time-msg-list. Then sort the list. | |
449 ;; | |
450 (let ((entry-list diary-entries-list) | |
451 (new-appts '())) | |
452 (while (and entry-list | |
453 (calendar-date-equal | |
454 (calendar-current-date) (car (car entry-list)))) | |
455 (let ((time-string (car (cdr (car entry-list))))) | |
456 (while (string-match | |
457 "\\`[ \t\n]*\\([0-9]?[0-9]\\(:[0-9][0-9]\\)?[ \t]*\\(am\\|pm\\)?\\|noon\\|midnight\\|midnite\\).*$" | |
458 time-string) | |
459 (let* ((eol (match-end 0)) | |
460 (appt-time-string | |
461 (substring time-string (match-beginning 1) | |
462 (match-end 1))) | |
463 (appt-msg-string | |
464 (substring time-string (match-end 1) eol)) | |
465 (appt-time (list (appt-convert-time appt-time-string)))) | |
466 (setq time-string (substring time-string eol) | |
467 new-appts (cons (cons appt-time | |
468 (list (concat appt-time-string ":" | |
469 appt-msg-string))) | |
470 new-appts)))) | |
471 (if appt-check-time-syntax | |
472 (while (string-match "\n*\\([^\n]+\\)$" time-string) | |
473 (beep) | |
474 (message "Unparsable time: %s" | |
475 (substring time-string (match-beginning 1) | |
476 (match-end 1))) | |
477 (sit-for 3) | |
478 (setq time-string (substring time-string (match-end 0))))) | |
479 | |
480 ) | |
481 (setq entry-list (cdr entry-list))) | |
482 (setq appt-time-msg-list ; seems we can't nconc this list... | |
483 (append (nreverse new-appts) appt-time-msg-list)))) | |
484 (setq appt-time-msg-list (appt-sort-list appt-time-msg-list)) | |
485 ;; | |
486 ;; Get the current time and convert it to minutes from midnight. ie. 12:01am | |
487 ;; = 1, midnight = 0, so that the elements in the list that are earlier than | |
488 ;; the present time can be removed. | |
489 ;; | |
490 (let ((cur-comp-time (appt-current-time-in-seconds)) | |
491 (appt-comp-time (car (car (car appt-time-msg-list))))) | |
492 (while (and appt-time-msg-list (< appt-comp-time cur-comp-time)) | |
493 (setq appt-time-msg-list (cdr appt-time-msg-list)) | |
494 (if appt-time-msg-list | |
495 (setq appt-comp-time (car (car (car appt-time-msg-list))))))) | |
496 appt-time-msg-list) | |
497 | |
498 | |
499 (defun appt-beep (&optional final-p) | |
500 (cond ((null appt-audible) nil) | |
501 ((numberp appt-audible) | |
502 (let ((i appt-audible)) | |
503 (while (> i 0) (beep) (setq i (1- i))))) | |
504 ((consp appt-audible) | |
505 (let ((i (car appt-audible)) | |
506 (j (cdr appt-audible))) | |
507 (if (consp j) (setq j (car j))) | |
508 (while (> i 0) | |
509 (if (fboundp 'play-sound) | |
510 (beep nil (if final-p 'appt-final 'appt)) | |
511 (beep)) | |
512 (sleep-for j) | |
513 (setq i (1- i))))) | |
514 (t (beep)))) | |
515 | |
516 | |
517 (defun appt-check () | |
518 "Check for an appointment and update the mode line and minibuffer if | |
519 desired. Note: the time must be the first thing in the line in the diary | |
520 for a warning to be issued. | |
521 The format of the time can be either 24 hour or am/pm. Example: | |
522 | |
523 02/23/89 | |
524 18:00 Dinner | |
525 Thursday | |
526 11:45am Lunch meeting. | |
527 | |
528 The following variables control the action of the notification: | |
529 | |
530 appt-issue-message If this variable is nil, then the code in this | |
531 file does nothing. | |
532 appt-msg-countdown-list Specifies how much warning you want before | |
533 appointments. | |
534 appt-audible Whether to beep when it's notification-time. | |
535 appt-display-mode-line Whether to display a countdown to the next | |
536 appointment in the mode-line. | |
537 appt-announce-method The function used to do the notifications. | |
538 'appt-window-announce to do it in a pop-up | |
539 window, 'appt-message-announce or | |
540 'appt-persistent-message-announce to do it | |
541 in the echo-area. | |
542 appt-display-duration If appt-announce-method is set to the function | |
543 'appt-window-announce, this specifies how many | |
544 seconds the pop-up window should stick around. | |
545 | |
546 This function is run from the `loadst' or `wakeup' process for display-time. | |
547 Therefore, you need to have (display-time) in your .emacs file." | |
548 (if appt-issue-message | |
549 (let ((min-to-app -1)) | |
550 ;; Get the current time and convert it to minutes | |
551 ;; from midnight. ie. 12:01am = 1, midnight = 0. | |
552 (let* ((cur-comp-time (appt-current-time-in-seconds)) | |
553 ;; If the current time is the same as the tick, just return. | |
554 ;; This means that this function has been called more than once | |
555 ;; in the current minute, which is not useful. | |
556 (shut-up-this-time (= cur-comp-time appt-check-tick)) | |
557 (turnover-p (> appt-check-tick cur-comp-time))) | |
558 (setq appt-check-tick cur-comp-time) | |
559 ;; | |
560 ;; If it is now the next day (we have crossed midnight since the last | |
561 ;; time this was called) then we should update our appointments to | |
562 ;; today's list. | |
563 (if turnover-p (appt-diary-entries)) | |
564 ;; | |
565 ;; Get the first time off of the list and calculate the number | |
566 ;; of minutes until the appointment. | |
567 (if appt-time-msg-list | |
568 (let ((appt-comp-time (car (car (car appt-time-msg-list))))) | |
569 (setq min-to-app (- appt-comp-time cur-comp-time)) | |
570 (while (and appt-time-msg-list (< appt-comp-time cur-comp-time)) | |
571 (setq appt-time-msg-list (cdr appt-time-msg-list)) | |
572 (if appt-time-msg-list | |
573 (setq appt-comp-time (car (car (car appt-time-msg-list)))))) | |
574 ;; | |
575 ;; If we have an appointment between midnight and warning-time | |
576 ;; minutes after midnight, we must begin to issue a message | |
577 ;; before midnight. Midnight is considered 0 minutes and 11:59pm | |
578 ;; is 1439 minutes. Therefore we must recalculate the minutes to | |
579 ;; appointment variable. It is equal to the number of minutes | |
580 ;; before midnight plus the number of minutes after midnight our | |
581 ;; appointment is. | |
582 ;; | |
583 ;; ## I don't think this does anything -- it would if it were | |
584 ;; (for example) a 12:01am appt on the list at 11:55pm, but that | |
585 ;; can't ever happen, because the applicable 12:01am appt is for | |
586 ;; tomorrow, not today, and we only have today's diary list. | |
587 ;; It's not simply a matter of concatenating two days together, | |
588 ;; either, because then tuesday's appts would be signalled on | |
589 ;; monday. We have to do a real one-day lookahead -- keep a list | |
590 ;; of tomorrow's appts, and check it when near midnight. | |
591 ;; | |
592 (if (and (< appt-comp-time (apply 'max appt-msg-countdown-list)) | |
593 (> (+ cur-comp-time (apply 'max appt-msg-countdown-list)) | |
594 max-time)) | |
595 (setq min-to-app (+ (- (1+ max-time) cur-comp-time)) | |
596 appt-comp-time)) | |
597 ;; | |
598 ;; issue warning if the appointment time is within warning-time | |
599 (cond | |
600 ;; if there should not be any notifications in the mode-line, | |
601 ;; clear it. | |
602 ((> min-to-app (apply 'max appt-msg-countdown-list)) | |
603 (appt-display-mode-line nil)) | |
604 ;; do nothing if this is the second time this minute we've | |
605 ;; gotten here, of if we shouldn't be notifying right now. | |
606 ((or shut-up-this-time | |
607 (and (not (= min-to-app 0)) | |
608 (not (memq min-to-app appt-msg-countdown-list)))) | |
609 nil) | |
610 | |
611 ((and (= min-to-app 0) | |
612 (string-match "%%(" (nth 1 (car appt-time-msg-list)))) | |
613 ;; | |
614 ;; If this is a magic evaluating-notification, evaluate it. | |
615 ;; these kinds of notifications aren't subject to the | |
616 ;; appt-msg-countdown-list. | |
617 ;; | |
618 (let* ((list-string (substring (nth 1 (car appt-time-msg-list)) | |
619 (1- (match-end 0)))) | |
620 (form (condition-case () | |
621 (read list-string) | |
622 (error | |
623 (ding) | |
624 (message "Appt: error reading from \"%s\"" | |
625 (nth 1 (car appt-time-msg-list))) | |
626 (sit-for 2) | |
627 nil)))) | |
628 (eval form))) | |
629 | |
630 ((and (<= min-to-app (apply 'max appt-msg-countdown-list)) | |
631 (>= min-to-app 0)) | |
632 ;; | |
633 ;; produce a notification. | |
634 (appt-beep (= min-to-app 0)) | |
635 (funcall appt-announce-method min-to-app | |
636 (car appt-time-msg-list)) | |
637 ;; update mode line and expire if necessary | |
638 (appt-display-mode-line min-to-app) | |
639 ;; if it's expired, remove it. | |
640 (if (= min-to-app 0) | |
641 (setq appt-time-msg-list (cdr appt-time-msg-list)))) | |
642 (t | |
643 ;; else we're not near any appointment, or there are no | |
644 ;; apointments; make sure mode line is clear. | |
645 (appt-display-mode-line nil)))) | |
646 (appt-display-mode-line nil)))))) | |
647 | |
648 | |
649 | |
650 ;;; Interactively adding and deleting appointments | |
651 | |
652 (defun appt-add (new-appt-time new-appt-msg) | |
653 "Adds an appointment to the list of appointments for the day at TIME | |
654 and issue MESSAGE. The time should be in either 24 hour format or | |
655 am/pm format. " | |
656 | |
657 (interactive "sTime (hh:mm[am/pm]): \nsMessage: ") | |
658 (if (string-match "[0-9]?[0-9]:[0-9][0-9]\\(am\\|pm\\)?" new-appt-time) | |
659 nil | |
660 (error "Unacceptable time-string")) | |
661 | |
662 (let* ((appt-time-string (concat new-appt-time " " new-appt-msg)) | |
663 (appt-time (list (appt-convert-time new-appt-time))) | |
664 (time-msg (cons appt-time (list appt-time-string)))) | |
665 (setq appt-time-msg-list (append appt-time-msg-list | |
666 (list time-msg))) | |
667 (setq appt-time-msg-list (appt-sort-list appt-time-msg-list)))) | |
668 | |
669 (defun appt-delete () | |
670 "Deletes an appointment from the list of appointments." | |
671 (interactive) | |
672 (let* ((tmp-msg-list appt-time-msg-list)) | |
673 (while tmp-msg-list | |
674 (let* ((element (car tmp-msg-list)) | |
675 (prompt-string (concat "Delete " | |
676 (prin1-to-string (car (cdr element))) | |
677 " from list? ")) | |
678 (test-input (y-or-n-p prompt-string))) | |
679 (setq tmp-msg-list (cdr tmp-msg-list)) | |
680 (if test-input | |
681 (setq appt-time-msg-list (delq element appt-time-msg-list))))) | |
682 (message ""))) | |
683 | |
684 | |
685 ;;; Patching in to existing time code to install our hook. | |
686 | |
687 (defvar display-time-hook nil | |
688 "*List of functions to be called when the time is updated on the mode line.") | |
689 | |
690 (setq display-time-hook 'appt-check) | |
691 | |
692 (defvar display-time-hook-installed nil) | |
693 | |
694 (defun install-display-time-hook () | |
695 (if display-time-hook-installed ;; only do this stuff once! | |
696 nil | |
697 (let ((old-fn (if (or (featurep 'reportmail) | |
698 ;; old reportmail without a provide statement | |
699 (and (fboundp 'display-time-filter-18-55) | |
700 (fboundp 'display-time-filter-18-57))) | |
701 (if (and (featurep 'itimer) ; XEmacs reportmail.el | |
702 (fboundp 'display-time-timer-function)) | |
703 'display-time-timer-function | |
704 ;; older reportmail, or no timer.el. | |
705 (if (string-match "18\\.5[0-5]" (emacs-version)) | |
706 'display-time-filter-18-55 | |
707 'display-time-filter-18-57)) | |
708 ;; othewise, time.el | |
709 (if (and (featurep 'itimer) | |
710 (fboundp 'display-time-function)) ; XEmacs | |
711 'display-time-function | |
712 'display-time-filter)))) | |
713 ;; we're about to redefine it... | |
714 (fset 'old-display-time-filter (symbol-function old-fn)) | |
715 (fset old-fn | |
716 (function (lambda (&rest args) ;; ...here's the revised definition | |
717 "Revised version of the original function: this version calls a hook." | |
718 (apply 'old-display-time-filter args) | |
719 (run-hooks 'display-time-hook))))) | |
720 (setq display-time-hook-installed t) | |
721 )) | |
722 | |
723 (provide 'appt) |