1 ;;; appt.el --- appointment notification functions.
3 ;; Copyright (C) 1989, 1990, 1994 Free Software Foundation, Inc.
5 ;; Author: Neil Mager <neilm@juliet.ll.mit.edu>
9 ;; This file is part of GNU Emacs.
11 ;; GNU Emacs is free software; you can redistribute it and/or modify
12 ;; it under the terms of the GNU General Public License as published by
13 ;; the Free Software Foundation; either version 2, or (at your option)
16 ;; GNU Emacs is distributed in the hope that it will be useful,
17 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
18 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 ;; GNU General Public License for more details.
21 ;; You should have received a copy of the GNU General Public License
22 ;; along with GNU Emacs; see the file COPYING. If not, write to
23 ;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
28 ;; appt.el - visible and/or audible notification of
29 ;; appointments from ~/diary file generated from
30 ;; Edward M. Reingold's calendar.el.
33 ;; Comments, corrections, and improvements should be sent to
35 ;; Net <neilm@juliet.ll.mit.edu>
36 ;; Voice (617) 981-4803
38 ;;; Thanks to Edward M. Reingold for much help and many suggestions,
39 ;;; And to many others for bug fixes and suggestions.
42 ;;; This functions in this file will alert the user of a
43 ;;; pending appointment based on their diary file.
46 ;;; ******* It is necessary to invoke 'display-time' ********
47 ;;; ******* and 'diary' for this to work properly. ********
49 ;;; A message will be displayed in the mode line of the emacs buffer
50 ;;; and (if the user desires) the terminal will beep and display a message
51 ;;; from the diary in the mini-buffer, or the user may select to
52 ;;; have a message displayed in a new buffer.
54 ;;; The variable 'appt-message-warning-time' allows the
55 ;;; user to specify how much notice they want before the appointment. The
56 ;;; variable 'appt-issue-message' specifies whether the user wants
57 ;;; to to be notified of a pending appointment.
59 ;;; In order to use, the following should be in your .emacs file in addition to
60 ;;; creating a diary file and invoking calendar:
63 ;;; (setq view-diary-entries-initially t)
64 ;;; (setq appt-issue-message t)
66 ;;; The following three lines are required:
68 ;;; (add-hook 'diary-hook 'appt-make-list)
71 ;;; This is an example of what can be in your diary file:
73 ;;; 9:30am Coffee break
76 ;;; Based upon the above lines in your .emacs and diary files,
77 ;;; the calendar and diary will be displayed when you enter
78 ;;; emacs and your appointments list will automatically be created.
79 ;;; You will then be reminded at 9:20am about your coffee break
80 ;;; and at 11:50am to go to lunch.
82 ;;; Use describe-function on appt-check for a description of other variables
83 ;;; that can be used to personalize the notification system.
85 ;;; In order to add or delete items from todays list, use appt-add
88 ;;; Additionally, the appointments list is recreated automatically
89 ;;; at 12:01am for those who do not logout every day or are programming
92 ;;; Brief internal description - Skip this if your not interested!
94 ;;; The function appt-check is run from the 'loadst' process which is started
95 ;;; by invoking (display-time). A temporary function below modifies
96 ;;; display-time-filter
97 ;;; (from original time.el) to include a hook which will invoke appt-check.
98 ;;; This will not be necessary in the next version of gnuemacs.
101 ;;; The function appt-make-list creates the appointments list which appt-check
102 ;;; reads. This is all done automatically.
103 ;;; It is invoked from the function list-diary-entries.
105 ;;; You can change the way the appointment window is created/deleted by
106 ;;; setting the variables
108 ;;; appt-disp-window-function
110 ;;; appt-delete-window-function
112 ;;; For instance, these variables can be set to functions that display
113 ;;; appointments in pop-up frames, which are lowered or iconified after
114 ;;; appt-display-interval seconds.
119 ;; Make sure calendar is loaded when we compile this.
123 (defvar appt-issue-message t
124 "*Non-nil means check for appointments in the diary buffer.
125 To be detected, the diary entry must have the time
126 as the first thing on a line.")
129 (defvar appt-message-warning-time
12
130 "*Time in minutes before an appointment that the warning begins.")
133 (defvar appt-audible t
134 "*Non-nil means beep to indicate appointment.")
137 (defvar appt-visible t
138 "*Non-nil means display appointment message in echo area.")
141 (defvar appt-display-mode-line t
142 "*Non-nil means display minutes to appointment and time on the mode line.")
145 (defvar appt-msg-window t
146 "*Non-nil means display appointment message in another window.")
149 (defvar appt-display-duration
10
150 "*The number of seconds an appointment message is displayed.")
153 (defvar appt-display-diary t
154 "*Non-nil means to display the next days diary on the screen.
155 This will occur at midnight when the appointment list is updated.")
157 (defvar appt-time-msg-list nil
158 "The list of appointments for today.
159 Use `appt-add' and `appt-delete' to add and delete appointments from list.
160 The original list is generated from the today's `diary-entries-list'.
161 The number before each time/message is the time in minutes from midnight.")
163 (defconst max-time
1439
164 "11:59pm in minutes - number of minutes in a day minus 1.")
166 (defvar appt-display-interval
3
167 "*Number of minutes to wait between checking the appointment list.")
169 (defvar appt-buffer-name
" *appt-buf*"
170 "Name of the appointments buffer.")
172 (defvar appt-disp-window-function
'appt-disp-window
173 "Function called to display appointment window.")
175 (defvar appt-delete-window-function
'appt-delete-window
176 "Function called to remove appointment window and buffer.")
179 "Check for an appointment and update the mode line.
180 Note: the time must be the first thing in the line in the diary
181 for a warning to be issued.
183 The format of the time can be either 24 hour or am/pm.
190 11:45am Lunch meeting.
192 The following variables control the action of the notification:
195 If T, the diary buffer is checked for appointments.
197 appt-message-warning-time
198 Variable used to determine if appointment message
202 Variable used to determine if appointment is audible.
206 Variable used to determine if appointment message should be
207 displayed in the mini-buffer. Default is t.
210 Variable used to determine if appointment message
211 should temporarily appear in another window. Mutually exclusive
214 appt-display-duration
215 The number of seconds an appointment message
216 is displayed in another window.
218 appt-display-interval
219 The number of minutes to wait between checking the appointments
222 appt-disp-window-function
223 Function called to display appointment window. You can customize
224 appt.el by setting this variable to a function different from the
225 one provided with this package.
227 appt-delete-window-function
228 Function called to remove appointment window and buffer. You can
229 customize appt.el by setting this variable to a function different
230 from the one provided with this package.
232 This function is run from the loadst process for display time.
233 Therefore, you need to have `(display-time)' in your .emacs file."
236 (if (or (= appt-display-interval
1)
237 ;; This is true every appt-display-interval minutes.
238 (= 0 (mod (/ (nth 1 (current-time)) 60) appt-display-interval
)))
239 (let ((min-to-app -
1)
243 ;; Get the current time and convert it to minutes
244 ;; from midnight. ie. 12:01am = 1, midnight = 0.
246 (let* ((cur-hour(string-to-int
247 (substring (current-time-string) 11 13)))
248 (cur-min (string-to-int
249 (substring (current-time-string) 14 16)))
250 (cur-comp-time (+ (* cur-hour
60) cur-min
)))
252 ;; At the first check after 12:01am, we should update our
253 ;; appointments to today's list.
255 (if (and (>= cur-comp-time
1)
256 (<= cur-comp-time appt-display-interval
))
257 (if (and view-diary-entries-initially appt-display-diary
)
259 (let ((diary-display-hook 'appt-make-list
))
262 ;; If there are entries in the list, and the
263 ;; user wants a message issued
264 ;; get the first time off of the list
265 ;; and calculate the number of minutes until
268 (if (and appt-issue-message appt-time-msg-list
)
269 (let ((appt-comp-time (car (car (car appt-time-msg-list
)))))
270 (setq min-to-app
(- appt-comp-time cur-comp-time
))
272 (while (and appt-time-msg-list
273 (< appt-comp-time cur-comp-time
))
274 (setq appt-time-msg-list
(cdr appt-time-msg-list
))
275 (if appt-time-msg-list
277 (car (car (car appt-time-msg-list
))))))
279 ;; If we have an appointment between midnight and
280 ;; 'appt-message-warning-time' minutes after midnight,
281 ;; we must begin to issue a message before midnight.
282 ;; Midnight is considered 0 minutes and 11:59pm is
283 ;; 1439 minutes. Therefore we must recalculate the minutes
284 ;; to appointment variable. It is equal to the number of
285 ;; minutes before midnight plus the number of
286 ;; minutes after midnight our appointment is.
288 (if (and (< appt-comp-time appt-message-warning-time
)
289 (> (+ cur-comp-time appt-message-warning-time
)
291 (setq min-to-app
(+ (- (1+ max-time
) cur-comp-time
))
294 ;; issue warning if the appointment time is
295 ;; within appt-message-warning time
297 (if (and (<= min-to-app appt-message-warning-time
)
303 "[0-9]?[0-9]:[0-9][0-9]\\(am\\|pm\\)?"
306 (setq new-time
(substring display-time-string
310 appt-disp-window-function
312 (car (cdr (car appt-time-msg-list
))))
315 (format "%d sec" appt-display-duration
)
317 appt-delete-window-function
))
322 (car (cdr (car appt-time-msg-list
)))))
327 (if appt-display-mode-line
330 "[0-9]?[0-9]:[0-9][0-9]\\(am\\|pm\\)?"
333 (setq new-time
(substring display-time-string
336 (setq display-time-string
338 min-to-app
" min. " new-time
" "))
340 ;; force mode line updates - from time.el
342 (save-excursion (set-buffer (other-buffer)))
343 (set-buffer-modified-p (buffer-modified-p))
347 (setq appt-time-msg-list
348 (cdr appt-time-msg-list
))))))))))))
351 ;; Display appointment message in a separate buffer.
352 (defun appt-disp-window (min-to-app new-time appt-msg
)
355 ;; Make sure we're not in the minibuffer
356 ;; before splitting the window.
358 (if (equal (selected-window) (minibuffer-window))
360 (select-window (other-window 1))
362 (select-frame (other-frame 1)))))
364 (let* ((this-buffer (current-buffer))
365 (this-window (selected-window))
366 (appt-disp-buf (set-buffer (get-buffer-create appt-buffer-name
))))
368 (appt-select-lowest-window)
371 (pop-to-buffer appt-disp-buf
)
372 (setq mode-line-format
373 (concat "-------------------- Appointment in "
374 min-to-app
" minutes. " new-time
" %-"))
375 (insert-string appt-msg
)
376 (shrink-window-if-larger-than-buffer (get-buffer-window appt-disp-buf
))
377 (set-buffer-modified-p nil
)
378 (select-window this-window
)
382 (defun appt-delete-window ()
383 "Function called to undisplay appointment messages.
384 Usually just deletes the appointment buffer."
385 (delete-window (get-buffer-window appt-buffer-name
))
386 (kill-buffer appt-buffer-name
)
390 ;; Select the lowest window on the frame.
391 (defun appt-select-lowest-window ()
392 (setq lowest-window
(selected-window))
393 (let* ((bottom-edge (car (cdr (cdr (cdr (window-edges))))))
394 (last-window (previous-window))
397 (let* ((this-window (next-window))
398 (next-bottom-edge (car (cdr (cdr (cdr
399 (window-edges this-window
)))))))
400 (if (< bottom-edge next-bottom-edge
)
402 (setq bottom-edge next-bottom-edge
)
403 (setq lowest-window this-window
)))
405 (select-window this-window
)
406 (if (eq last-window this-window
)
408 (select-window lowest-window
)
409 (setq window-search nil
)))))))
412 (defun appt-add (new-appt-time new-appt-msg
)
413 "Add an appointment for the day at TIME and issue MESSAGE.
414 The time should be in either 24 hour format or am/pm format."
416 (interactive "sTime (hh:mm[am/pm]): \nsMessage: ")
417 (if (string-match "[0-9]?[0-9]:[0-9][0-9]\\(am\\|pm\\)?" new-appt-time
)
419 (error "Unacceptable time-string"))
421 (let* ((appt-time-string (concat new-appt-time
" " new-appt-msg
))
422 (appt-time (list (appt-convert-time new-appt-time
)))
423 (time-msg (cons appt-time
(list appt-time-string
))))
424 (setq appt-time-msg-list
(append appt-time-msg-list
426 (setq appt-time-msg-list
(appt-sort-list appt-time-msg-list
))))
428 (defun appt-delete ()
429 "Delete an appointment from the list of appointments."
431 (let* ((tmp-msg-list appt-time-msg-list
))
433 (let* ((element (car tmp-msg-list
))
434 (prompt-string (concat "Delete "
435 (prin1-to-string (car (cdr element
)))
437 (test-input (y-or-n-p prompt-string
)))
438 (setq tmp-msg-list
(cdr tmp-msg-list
))
440 (setq appt-time-msg-list
(delq element appt-time-msg-list
)))
441 (setq tmp-appt-msg-list nil
)))
445 ;; Create the appointments list from todays diary buffer.
446 ;; The time must be at the beginning of a line for it to be
447 ;; put in the appointments list.
451 ;; 10:00am group meeting
452 ;; We assume that the variables DATE and NUMBER
453 ;; hold the arguments that list-diary-entries received.
454 ;; They specify the range of dates that the diary is being processed for.
457 (defun appt-make-list ()
458 ;; We have something to do if the range of dates that the diary is
459 ;; considering includes the current date.
460 (if (and (not (calendar-date-compare
461 (list (calendar-current-date))
462 (list original-date
)))
463 (calendar-date-compare
464 (list (calendar-current-date))
465 (list (calendar-gregorian-from-absolute
466 (+ (calendar-absolute-from-gregorian original-date
)
469 ;; Clear the appointments list, then fill it in from the diary.
470 (setq appt-time-msg-list nil
)
471 (if diary-entries-list
473 ;; Cycle through the entry-list (diary-entries-list)
474 ;; looking for entries beginning with a time. If
475 ;; the entry begins with a time, add it to the
476 ;; appt-time-msg-list. Then sort the list.
478 (let ((entry-list diary-entries-list
)
479 (new-time-string ""))
480 ;; Skip diary entries for dates before today.
481 (while (and entry-list
482 (calendar-date-compare
483 (car entry-list
) (list (calendar-current-date))))
484 (setq entry-list
(cdr entry-list
)))
485 ;; Parse the entries for today.
486 (while (and entry-list
488 (calendar-current-date) (car (car entry-list
))))
489 (let ((time-string (substring (prin1-to-string
490 (cdr (car entry-list
))) 2 -
2)))
493 "[0-9]?[0-9]:[0-9][0-9]\\(am\\|pm\\)?.*"
495 (let* ((appt-time-string (substring time-string
499 (if (< (match-end 0) (length time-string
))
500 (setq new-time-string
(substring time-string
503 (setq new-time-string
""))
505 (string-match "[0-9]?[0-9]:[0-9][0-9]\\(am\\|pm\\)?"
508 (let* ((appt-time (list (appt-convert-time
509 (substring time-string
512 (time-msg (cons appt-time
513 (list appt-time-string
))))
514 (setq time-string new-time-string
)
515 (setq appt-time-msg-list
(append appt-time-msg-list
516 (list time-msg
)))))))
517 (setq entry-list
(cdr entry-list
)))))
518 (setq appt-time-msg-list
(appt-sort-list appt-time-msg-list
))
520 ;; Get the current time and convert it to minutes
521 ;; from midnight. ie. 12:01am = 1, midnight = 0,
522 ;; so that the elements in the list
523 ;; that are earlier than the present time can
526 (let* ((cur-hour(string-to-int
527 (substring (current-time-string) 11 13)))
528 (cur-min (string-to-int
529 (substring (current-time-string) 14 16)))
530 (cur-comp-time (+ (* cur-hour
60) cur-min
))
531 (appt-comp-time (car (car (car appt-time-msg-list
)))))
533 (while (and appt-time-msg-list
(< appt-comp-time cur-comp-time
))
534 (setq appt-time-msg-list
(cdr appt-time-msg-list
))
535 (if appt-time-msg-list
536 (setq appt-comp-time
(car (car (car appt-time-msg-list
))))))))))
539 ;;Simple sort to put the appointments list in order.
540 ;;Scan the list for the smallest element left in the list.
541 ;;Append the smallest element left into the new list, and remove
542 ;;it from the original list.
543 (defun appt-sort-list (appt-list)
544 (let ((order-list nil
))
546 (let* ((element (car appt-list
))
547 (element-time (car (car element
)))
548 (tmp-list (cdr appt-list
)))
550 (if (< element-time
(car (car (car tmp-list
))))
552 (setq element
(car tmp-list
))
553 (setq element-time
(car (car element
))))
554 (setq tmp-list
(cdr tmp-list
)))
555 (setq order-list
(append order-list
(list element
)))
556 (setq appt-list
(delq element appt-list
))))
560 (defun appt-convert-time (time2conv)
561 "Convert hour:min[am/pm] format to minutes from midnight."
567 (string-match ":[0-9][0-9]" time2conv
)
568 (setq min
(string-to-int
570 (+ (match-beginning 0) 1) (match-end 0))))
572 (string-match "[0-9]?[0-9]:" time2conv
)
573 (setq hr
(string-to-int
578 ;; convert the time appointment time into 24 hour time
580 (if (and (string-match "[p][m]" time2conv
) (< hr
12))
582 (string-match "[0-9]?[0-9]:" time2conv
)
583 (setq hr
(+ 12 hr
))))
585 ;; convert the actual time
586 ;; into minutes for comparison
587 ;; against the actual time.
589 (setq conv-time
(+ (* hr
60) min
))
592 (add-hook 'display-time-hook
'appt-check
)
594 ;;; appt.el ends here