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