*** empty log message ***
[emacs.git] / lisp / calendar / appt.el
blobc93395323492ff42fc858092be63a31ffcb04386
1 ;;; appt.el --- appointment notification functions.
3 ;; Author: Neil Mager <neilm@juliet.ll.mit.edu>
4 ;; Version: 2.1
5 ;; Last-Modified: 14 Jul 1992
6 ;; Keywords: calendar
8 ;; Copyright (C) 1989, 1990 Free Software Foundation, Inc.
10 ;; This file is part of GNU Emacs.
12 ;; GNU Emacs is free software; you can redistribute it and/or modify
13 ;; it under the terms of the GNU General Public License as published by
14 ;; the Free Software Foundation; either version 2, or (at your option)
15 ;; any later version.
17 ;; GNU Emacs is distributed in the hope that it will be useful,
18 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
19 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 ;; GNU General Public License for more details.
22 ;; You should have received a copy of the GNU General Public License
23 ;; along with GNU Emacs; see the file COPYING. If not, write to
24 ;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
26 ;;; Commentary:
29 ;; appt.el - visible and/or audible notification of
30 ;; appointments from ~/diary file generated from
31 ;; Edward M. Reingold's calendar.el.
34 ;; Comments, corrections, and improvements should be sent to
35 ;; Neil M. Mager
36 ;; Net <neilm@juliet.ll.mit.edu>
37 ;; Voice (617) 981-4803
38 ;;;
39 ;;; Thanks to Edward M. Reingold for much help and many suggestions,
40 ;;; And to many others for bug fixes and suggestions.
41 ;;;
42 ;;;
43 ;;; This functions in this file will alert the user of a
44 ;;; pending appointment based on their diary file.
45 ;;;
46 ;;;
47 ;;; ******* It is necessary to invoke 'display-time' ********
48 ;;; ******* and 'diary' for this to work properly. ********
49 ;;;
50 ;;; A message will be displayed in the mode line of the emacs buffer
51 ;;; and (if the user desires) the terminal will beep and display a message
52 ;;; from the diary in the mini-buffer, or the user may select to
53 ;;; have a message displayed in a new buffer.
54 ;;;
55 ;;; The variable 'appt-message-warning-time' allows the
56 ;;; user to specify how much notice they want before the appointment. The
57 ;;; variable 'appt-issue-message' specifies whether the user wants
58 ;;; to to be notified of a pending appointment.
59 ;;;
60 ;;; In order to use, the following should be in your .emacs file in addition to
61 ;;; creating a diary file and invoking calendar:
62 ;;;
63 ;;; Set some options
64 ;;; (setq view-diary-entries-initially t)
65 ;;; (setq appt-issue-message t)
66 ;;;
67 ;;; The following three lines are required:
68 ;;; (display-time)
69 ;;; (autoload 'appt-make-list "appt.el" nil t)
70 ;;; (setq diary-display-hook
71 ;;; (list 'appt-make-list 'prepare-fancy-diary-buffer))
72 ;;;
73 ;;;
74 ;;; This is an example of what can be in your diary file:
75 ;;; Monday
76 ;;; 9:30am Coffee break
77 ;;; 12:00pm Lunch
78 ;;;
79 ;;; Based upon the above lines in your .emacs and diary files,
80 ;;; the calendar and diary will be displayed when you enter
81 ;;; emacs and your appointments list will automatically be created.
82 ;;; You will then be reminded at 9:20am about your coffee break
83 ;;; and at 11:50am to go to lunch.
84 ;;;
85 ;;; Use describe-function on appt-check for a description of other variables
86 ;;; that can be used to personalize the notification system.
87 ;;;
88 ;;; In order to add or delete items from todays list, use appt-add
89 ;;; and appt-delete.
90 ;;;
91 ;;; Additionally, the appointments list is recreated automatically
92 ;;; at 12:01am for those who do not logout every day or are programming
93 ;;; late.
94 ;;;
95 ;;; Brief internal description - Skip this if your not interested!
96 ;;;
97 ;;; The function appt-check is run from the 'loadst' process which is started
98 ;;; by invoking (display-time). A temporary function below modifies
99 ;;; display-time-filter
100 ;;; (from original time.el) to include a hook which will invoke appt-check.
101 ;;; This will not be necessary in the next version of gnuemacs.
104 ;;; The function appt-make-list creates the appointments list which appt-check
105 ;;; reads. This is all done automatically.
106 ;;; It is invoked from the function list-diary-entries.
109 ;;; Code:
111 (defvar appt-issue-message t
112 "*Non-nil means check for appointments in the diary buffer.
113 To be detected, the diary entry must have the time
114 as the first thing on a line.")
116 (defvar appt-message-warning-time 10
117 "*Time in minutes before an appointment that the warning begins.")
119 (defvar appt-audible t
120 "*Non-nil means beep to indicate appointment.")
122 (defvar appt-visible t
123 "*Non-nil means display appointment message in echo area.")
125 (defvar appt-display-mode-line t
126 "*Non-nil means display minutes to appointment and time on the mode line.")
128 (defvar appt-msg-window t
129 "*Non-nil means display appointment message in another window.")
131 (defvar appt-display-duration 5
132 "*The number of seconds an appointment message is displayed.")
134 (defvar appt-display-diary t
135 "*Non-nil means to display the next days diary on the screen.
136 This will occur at midnight when the appointment list is updated.")
138 (defvar appt-time-msg-list nil
139 "The list of appointments for today.
140 Use `appt-add' and `appt-delete' to add and delete appointments from list.
141 The original list is generated from the today's `diary-entries-list'.
142 The number before each time/message is the time in minutes from midnight.")
144 (defconst max-time 1439
145 "11:59pm in minutes - number of minutes in a day minus 1.")
147 (defun appt-check ()
148 "Check for an appointment and update the mode line.
149 Note: the time must be the first thing in the line in the diary
150 for a warning to be issued.
152 The format of the time can be either 24 hour or am/pm.
153 Example:
155 02/23/89
156 18:00 Dinner
158 Thursday
159 11:45am Lunch meeting.
161 The following variables control the action of the notification:
163 appt-issue-message
164 If T, the diary buffer is checked for appointments.
166 appt-message-warning-time
167 Variable used to determine if appointment message
168 should be displayed.
170 appt-audible
171 Variable used to determine if appointment is audible.
172 Default is t.
174 appt-visible
175 Variable used to determine if appointment message should be
176 displayed in the mini-buffer. Default is t.
178 appt-msg-window
179 Variable used to determine if appointment message
180 should temporarily appear in another window. Mutually exclusive
181 to appt-visible.
183 appt-display-duration
184 The number of seconds an appointment message
185 is displayed in another window.
187 This function is run from the loadst process for display time.
188 Therefore, you need to have `(display-time)' in your .emacs file."
191 (let ((min-to-app -1)
192 (new-time ""))
193 (save-excursion
195 ;; Get the current time and convert it to minutes
196 ;; from midnight. ie. 12:01am = 1, midnight = 0.
198 (let* ((cur-hour(string-to-int
199 (substring (current-time-string) 11 13)))
200 (cur-min (string-to-int
201 (substring (current-time-string) 14 16)))
202 (cur-comp-time (+ (* cur-hour 60) cur-min)))
204 ;; If the time is 12:01am, we should update our
205 ;; appointments to todays list.
207 (if (= cur-comp-time 1)
208 (if (and view-diary-entries-initially appt-display-diary)
209 (diary)
210 (let ((diary-display-hook 'appt-make-list))
211 (diary))))
213 ;; If there are entries in the list, and the
214 ;; user wants a message issued
215 ;; get the first time off of the list
216 ;; and calculate the number of minutes until
217 ;; the appointment.
219 (if (and appt-issue-message appt-time-msg-list)
220 (let ((appt-comp-time (car (car (car appt-time-msg-list)))))
221 (setq min-to-app (- appt-comp-time cur-comp-time))
223 (while (and appt-time-msg-list
224 (< appt-comp-time cur-comp-time))
225 (setq appt-time-msg-list (cdr appt-time-msg-list))
226 (if appt-time-msg-list
227 (setq appt-comp-time
228 (car (car (car appt-time-msg-list))))))
230 ;; If we have an appointment between midnight and
231 ;; 'appt-message-warning-time' minutes after midnight,
232 ;; we must begin to issue a message before midnight.
233 ;; Midnight is considered 0 minutes and 11:59pm is
234 ;; 1439 minutes. Therefore we must recalculate the minutes
235 ;; to appointment variable. It is equal to the number of
236 ;; minutes before midnight plus the number of
237 ;; minutes after midnight our appointment is.
239 (if (and (< appt-comp-time appt-message-warning-time)
240 (> (+ cur-comp-time appt-message-warning-time)
241 max-time))
242 (setq min-to-app (+ (- (1+ max-time) cur-comp-time))
243 appt-comp-time))
245 ;; issue warning if the appointment time is
246 ;; within appt-message-warning time
248 (if (and (<= min-to-app appt-message-warning-time)
249 (>= min-to-app 0))
250 (progn
251 (if appt-msg-window
252 (progn
253 (string-match
254 "[0-9]?[0-9]:[0-9][0-9]\\(am\\|pm\\)?"
255 display-time-string)
257 (setq new-time (substring display-time-string
258 (match-beginning 0)
259 (match-end 0)))
260 (appt-disp-window min-to-app new-time
261 (car (cdr (car appt-time-msg-list)))))
262 ;;; else
264 (if appt-visible
265 (message "%s"
266 (car (cdr (car appt-time-msg-list)))))
268 (if appt-audible
269 (beep 1)))
271 (if appt-display-mode-line
272 (progn
273 (string-match
274 "[0-9]?[0-9]:[0-9][0-9]\\(am\\|pm\\)?"
275 display-time-string)
277 (setq new-time (substring display-time-string
278 (match-beginning 0)
279 (match-end 0)))
280 (setq display-time-string
281 (concat "App't in "
282 min-to-app " min. " new-time " "))
284 ;; force mode line updates - from time.el
286 (save-excursion (set-buffer (other-buffer)))
287 (set-buffer-modified-p (buffer-modified-p))
288 (sit-for 0)))
290 (if (= min-to-app 0)
291 (setq appt-time-msg-list
292 (cdr appt-time-msg-list)))))))))))
295 ;; Display appointment message in a separate buffer.
296 (defun appt-disp-window (min-to-app new-time appt-msg)
297 (require 'electric)
298 (save-window-excursion
300 ;; Make sure we're not in the minibuffer
301 ;; before splitting the window.
303 (if (= (frame-height)
304 (nth 3 (window-edges (selected-window))))
306 (appt-select-lowest-window)
307 (split-window))
309 (let* ((this-buffer (current-buffer))
310 (appt-disp-buf (set-buffer (get-buffer-create "appt-buf"))))
311 (setq mode-line-format
312 (concat "-------------------- Appointment in "
313 min-to-app " minutes. " new-time " %-"))
314 (pop-to-buffer appt-disp-buf)
315 (insert-string appt-msg)
316 (shrink-window-if-larger-than-buffer (get-buffer-window appt-disp-buf))
317 (set-buffer-modified-p nil)
318 (if appt-audible
319 (beep 1))
320 (sit-for appt-display-duration)
321 (if appt-audible
322 (beep 1))
323 (kill-buffer appt-disp-buf))))
325 ;; Select the lowest window on the frame.
326 (defun appt-select-lowest-window ()
327 (setq lowest-window (selected-window))
328 (let* ((bottom-edge (car (cdr (cdr (cdr (window-edges))))))
329 (last-window (previous-window))
330 (window-search t))
331 (while window-search
332 (let* ((this-window (next-window))
333 (next-bottom-edge (car (cdr (cdr (cdr
334 (window-edges this-window)))))))
335 (if (< bottom-edge next-bottom-edge)
336 (progn
337 (setq bottom-edge next-bottom-edge)
338 (setq lowest-window this-window)))
340 (select-window this-window)
341 (if (eq last-window this-window)
342 (progn
343 (select-window lowest-window)
344 (setq window-search nil)))))))
347 (defun appt-add (new-appt-time new-appt-msg)
348 "Add an appointment for the day at TIME and issue MESSAGE.
349 The time should be in either 24 hour format or am/pm format."
351 (interactive "sTime (hh:mm[am/pm]): \nsMessage: ")
352 (if (string-match "[0-9]?[0-9]:[0-9][0-9]\\(am\\|pm\\)?" new-appt-time)
354 (error "Unacceptable time-string"))
356 (let* ((appt-time-string (concat new-appt-time " " new-appt-msg))
357 (appt-time (list (appt-convert-time new-appt-time)))
358 (time-msg (cons appt-time (list appt-time-string))))
359 (setq appt-time-msg-list (append appt-time-msg-list
360 (list time-msg)))
361 (setq appt-time-msg-list (appt-sort-list appt-time-msg-list))))
363 (defun appt-delete ()
364 "Delete an appointment from the list of appointments."
365 (interactive)
366 (let* ((tmp-msg-list appt-time-msg-list))
367 (while tmp-msg-list
368 (let* ((element (car tmp-msg-list))
369 (prompt-string (concat "Delete "
370 (prin1-to-string (car (cdr element)))
371 " from list? "))
372 (test-input (y-or-n-p prompt-string)))
373 (setq tmp-msg-list (cdr tmp-msg-list))
374 (if test-input
375 (setq appt-time-msg-list (delq element appt-time-msg-list)))
376 (setq tmp-appt-msg-list nil)))
377 (message "")))
380 ;; Create the appointments list from todays diary buffer.
381 ;; The time must be at the beginning of a line for it to be
382 ;; put in the appointments list.
383 ;; 02/23/89
384 ;; 12:00pm lunch
385 ;; Wednesday
386 ;; 10:00am group meeting"
388 (defun appt-make-list ()
389 (setq appt-time-msg-list nil)
391 (save-excursion
392 (if diary-entries-list
394 ;; Cycle through the entry-list (diary-entries-list)
395 ;; looking for entries beginning with a time. If
396 ;; the entry begins with a time, add it to the
397 ;; appt-time-msg-list. Then sort the list.
399 (let ((entry-list diary-entries-list)
400 (new-time-string ""))
401 (while (and entry-list
402 (calendar-date-equal
403 (calendar-current-date) (car (car entry-list))))
404 (let ((time-string (substring (prin1-to-string
405 (cdr (car entry-list))) 2 -2)))
407 (while (string-match
408 "[0-9]?[0-9]:[0-9][0-9]\\(am\\|pm\\)?.*"
409 time-string)
410 (let* ((appt-time-string (substring time-string
411 (match-beginning 0)
412 (match-end 0))))
414 (if (< (match-end 0) (length time-string))
415 (setq new-time-string (substring time-string
416 (+ (match-end 0) 1)
417 nil))
418 (setq new-time-string ""))
420 (string-match "[0-9]?[0-9]:[0-9][0-9]\\(am\\|pm\\)?"
421 time-string)
423 (let* ((appt-time (list (appt-convert-time
424 (substring time-string
425 (match-beginning 0)
426 (match-end 0)))))
427 (time-msg (cons appt-time
428 (list appt-time-string))))
429 (setq time-string new-time-string)
430 (setq appt-time-msg-list (append appt-time-msg-list
431 (list time-msg)))))))
432 (setq entry-list (cdr entry-list)))))
433 (setq appt-time-msg-list (appt-sort-list appt-time-msg-list))
435 ;; Get the current time and convert it to minutes
436 ;; from midnight. ie. 12:01am = 1, midnight = 0,
437 ;; so that the elements in the list
438 ;; that are earlier than the present time can
439 ;; be removed.
441 (let* ((cur-hour(string-to-int
442 (substring (current-time-string) 11 13)))
443 (cur-min (string-to-int
444 (substring (current-time-string) 14 16)))
445 (cur-comp-time (+ (* cur-hour 60) cur-min))
446 (appt-comp-time (car (car (car appt-time-msg-list)))))
448 (while (and appt-time-msg-list (< appt-comp-time cur-comp-time))
449 (setq appt-time-msg-list (cdr appt-time-msg-list))
450 (if appt-time-msg-list
451 (setq appt-comp-time (car (car (car appt-time-msg-list)))))))))
454 ;;Simple sort to put the appointments list in order.
455 ;;Scan the list for the smallest element left in the list.
456 ;;Append the smallest element left into the new list, and remove
457 ;;it from the original list.
458 (defun appt-sort-list (appt-list)
459 (let ((order-list nil))
460 (while appt-list
461 (let* ((element (car appt-list))
462 (element-time (car (car element)))
463 (tmp-list (cdr appt-list)))
464 (while tmp-list
465 (if (< element-time (car (car (car tmp-list))))
467 (setq element (car tmp-list))
468 (setq element-time (car (car element))))
469 (setq tmp-list (cdr tmp-list)))
470 (setq order-list (append order-list (list element)))
471 (setq appt-list (delq element appt-list))))
472 order-list))
475 (defun appt-convert-time (time2conv)
476 "Convert hour:min[am/pm] format to minutes from midnight."
478 (let ((conv-time 0)
479 (hr 0)
480 (min 0))
482 (string-match ":[0-9][0-9]" time2conv)
483 (setq min (string-to-int
484 (substring time2conv
485 (+ (match-beginning 0) 1) (match-end 0))))
487 (string-match "[0-9]?[0-9]:" time2conv)
488 (setq hr (string-to-int
489 (substring time2conv
490 (match-beginning 0)
491 (match-end 0))))
493 ;; convert the time appointment time into 24 hour time
495 (if (and (string-match "[p][m]" time2conv) (< hr 12))
496 (progn
497 (string-match "[0-9]?[0-9]:" time2conv)
498 (setq hr (+ 12 hr))))
500 ;; convert the actual time
501 ;; into minutes for comparison
502 ;; against the actual time.
504 (setq conv-time (+ (* hr 60) min))
505 conv-time))
507 (setq display-time-hook 'appt-check)
509 ;;; appt.el ends here