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