* INSTALL: Mention yum-builddep.
[bpt/emacs.git] / lisp / calendar / appt.el
CommitLineData
55535639 1;;; appt.el --- appointment notification functions
c0274f38 2
95df8112
GM
3;; Copyright (C) 1989-1990, 1994, 1998, 2001-2011
4;; Free Software Foundation, Inc.
3a801d0c 5
e5167999 6;; Author: Neil Mager <neilm@juliet.ll.mit.edu>
aff88519 7;; Maintainer: Glenn Morris <rgm@gnu.org>
e5167999 8;; Keywords: calendar
bd78fa1d 9;; Package: calendar
e5167999 10
902a0e3c
JB
11;; This file is part of GNU Emacs.
12
2ed66575 13;; GNU Emacs is free software: you can redistribute it and/or modify
902a0e3c 14;; it under the terms of the GNU General Public License as published by
2ed66575
GM
15;; the Free Software Foundation, either version 3 of the License, or
16;; (at your option) any later version.
902a0e3c
JB
17
18;; GNU Emacs is distributed in the hope that it will be useful,
19;; but WITHOUT ANY WARRANTY; without even the implied warranty of
20;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21;; GNU General Public License for more details.
22
23;; You should have received a copy of the GNU General Public License
2ed66575 24;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
902a0e3c 25
e5167999
ER
26;;; Commentary:
27
902a0e3c
JB
28;;
29;; appt.el - visible and/or audible notification of
8d638c1b 30;; appointments from diary file.
902a0e3c 31;;
e1422141
SM
32;;
33;; Thanks to Edward M. Reingold for much help and many suggestions,
34;; And to many others for bug fixes and suggestions.
35;;
36;;
37;; This functions in this file will alert the user of a
38;; pending appointment based on his/her diary file. This package
39;; is documented in the Emacs manual.
40;;
41;; To activate this package, simply use (appt-activate 1).
42;; A `diary-file' with appointments of the format described in the
43;; documentation of the function `appt-check' is required.
44;; Relevant customizable variables are also listed in the
45;; documentation of that function.
46;;
47;; Today's appointment list is initialized from the diary when this
12b2a018 48;; package is activated. Additionally, the appointments list is
e1422141 49;; recreated automatically at 12:01am for those who do not logout
12b2a018 50;; every day or are programming late. It is also updated when the
08151ec5
GM
51;; `diary-file' (or a file it includes) is saved. Calling
52;; `appt-check' with an argument (or re-enabling the package) forces a
53;; re-initialization at any time.
e1422141
SM
54;;
55;; In order to add or delete items from today's list, without
56;; changing the diary file, use `appt-add' and `appt-delete'.
57;;
58
59;; Brief internal description - Skip this if you are not interested!
60;;
61;; The function `appt-make-list' creates the appointments list which
62;; `appt-check' reads.
63;;
64;; You can change the way the appointment window is created/deleted by
3952e9d8
GM
65;; setting the variables `appt-disp-window-function' and
66;; `appt-delete-window-function'. For instance, you could be set them
67;; to functions that display appointments in pop-up frames, which are
68;; lowered or iconified after `appt-display-interval' minutes.
e1422141 69;;
e5167999
ER
70
71;;; Code:
72
467f174b 73(require 'diary-lib)
6afadb57 74
d589fa52
GM
75
76(defgroup appt nil
77 "Appointment notification."
467f174b 78 :prefix "appt-"
d589fa52
GM
79 :group 'calendar)
80
8db540c5 81(defcustom appt-message-warning-time 12
3952e9d8
GM
82 "Default time in minutes before an appointment that the warning begins.
83You probably want to make `appt-display-interval' a factor of this."
8db540c5
RS
84 :type 'integer
85 :group 'appt)
902a0e3c 86
5006e634
GM
87(defcustom appt-warning-time-regexp "warntime \\([0-9]+\\)"
88 "Regexp matching a string giving the warning time for an appointment.
89The first subexpression matches the time in minutes (an integer).
90This overrides the default `appt-message-warning-time'.
91You may want to put this inside a diary comment (see `diary-comment-start').
92For example, to be warned 30 minutes in advance of an appointment:
93 2011/06/01 12:00 Do something ## warntime 30
94"
95 :version "24.1"
96 :type 'regexp
97 :group 'appt)
98
8db540c5 99(defcustom appt-audible t
40f79f5b 100 "Non-nil means beep to indicate appointment."
8db540c5
RS
101 :type 'boolean
102 :group 'appt)
902a0e3c 103
8d638c1b 104;; TODO - add popup.
3536dea8 105(defcustom appt-display-format 'window
8d638c1b
GM
106 "How appointment reminders should be displayed.
107The options are:
108 window - use a separate window
109 echo - use the echo area
110 nil - no visible reminder.
3536dea8 111See also `appt-audible' and `appt-display-mode-line'."
8d638c1b
GM
112 :type '(choice
113 (const :tag "Separate window" window)
114 (const :tag "Echo-area" echo)
3536dea8 115 (const :tag "No visible display" nil))
8d638c1b 116 :group 'appt
3536dea8 117 :version "24.1") ; no longer inherit from deleted obsolete variables
8d638c1b 118
8d638c1b 119(defcustom appt-display-mode-line t
40f79f5b 120 "Non-nil means display minutes to appointment and time on the mode line.
3952e9d8
GM
121This is in addition to any other display of appointment messages.
122The mode line updates every minute, indepedent of the value of
123`appt-display-interval'."
8db540c5
RS
124 :type 'boolean
125 :group 'appt)
902a0e3c 126
8db540c5 127(defcustom appt-display-duration 10
40f79f5b 128 "The number of seconds an appointment message is displayed.
8d638c1b 129Only relevant if reminders are to be displayed in their own window."
8db540c5
RS
130 :type 'integer
131 :group 'appt)
902a0e3c 132
8db540c5 133(defcustom appt-display-diary t
40f79f5b 134 "Non-nil displays the diary when the appointment list is first initialized.
3952e9d8
GM
135This occurs when this package is first activated, and then at
136midnight when the appointment list updates."
8db540c5
RS
137 :type 'boolean
138 :group 'appt)
902a0e3c 139
8db540c5 140(defcustom appt-display-interval 3
3952e9d8
GM
141 "Interval in minutes at which to display appointment reminders.
142Once an appointment becomes due, Emacs displays reminders every
143`appt-display-interval' minutes. You probably want to make
144`appt-message-warning-time' be a multiple of this, so that you get
145a final message displayed precisely when the appointment is due.
146
147Note that this variable controls the interval at which
148`appt-display-message' is called. The mode line display (if active)
149always updates every minute."
8db540c5
RS
150 :type 'integer
151 :group 'appt)
a1506d29 152
8d638c1b
GM
153(defcustom appt-disp-window-function 'appt-disp-window
154 "Function called to display appointment window.
ce5b3019
GM
155Only relevant if reminders are being displayed in a window.
156It should take three string arguments: the number of minutes till
157the appointment, the current time, and the text of the appointment."
3952e9d8 158 :type 'function
8d638c1b
GM
159 :group 'appt)
160
161(defcustom appt-delete-window-function 'appt-delete-window
162 "Function called to remove appointment window and buffer.
163Only relevant if reminders are being displayed in a window."
3952e9d8 164 :type 'function
8d638c1b
GM
165 :group 'appt)
166
167
168;;; Internal variables below this point.
169
e1422141 170(defconst appt-buffer-name "*appt-buf*"
5b586155 171 "Name of the appointments buffer.")
a1506d29 172
d7cd4abb
GM
173;; TODO Turn this into an alist? It would be easier to add more
174;; optional elements.
4accbca6
GM
175;; Why is the first element (MINUTES) rather than just MINUTES?
176;; It may just inherit from diary-entries-list, where we have
177;; ((MONTH DAY YEAR) ENTRY)
8d638c1b
GM
178(defvar appt-time-msg-list nil
179 "The list of appointments for today.
180Use `appt-add' and `appt-delete' to add and delete appointments.
181The original list is generated from today's `diary-entries-list', and
182can be regenerated using the function `appt-check'.
d7cd4abb 183Each element of the generated list has the form
4accbca6 184\((MINUTES) STRING [FLAG] [WARNTIME])
d7cd4abb
GM
185where MINUTES is the time in minutes of the appointment after midnight,
186and STRING is the description of the appointment.
915d1300
GM
187FLAG and WARNTIME are not always present. A non-nil FLAG
188indicates that the element was made with `appt-add', so calling
189`appt-make-list' again should preserve it. If WARNTIME is non-nil,
190it is an integer to use in place of `appt-message-warning-time'.")
a1506d29 191
0cb7f2c0 192(defconst appt-max-time (1- (* 24 60))
8d638c1b 193 "11:59pm in minutes - number of minutes in a day minus 1.")
b570e652 194
efa434d9 195(defvar appt-mode-string nil
f3e7c0dc 196 "String being displayed in the mode line saying you have an appointment.
8d638c1b
GM
197The actual string includes the amount of time till the appointment.
198Only used if `appt-display-mode-line' is non-nil.")
464e8258 199(put 'appt-mode-string 'risky-local-variable t) ; for 'face property
f3e7c0dc
KH
200
201(defvar appt-prev-comp-time nil
3952e9d8 202 "Time of day (mins since midnight) at which we last checked appointments.")
f3e7c0dc 203
3952e9d8 204(defvar appt-display-count 0
8d638c1b 205 "Internal variable used to count number of consecutive reminders.")
efa434d9 206
8d638c1b
GM
207(defvar appt-timer nil
208 "Timer used for diary appointment notifications (`appt-check').
209If this is non-nil, appointment checking is active.")
210
211
212;;; Functions.
213
214(defun appt-display-message (string mins)
215 "Display a reminder about an appointment.
216The string STRING describes the appointment, due in integer MINS minutes.
217The format of the visible reminder is controlled by `appt-display-format'.
218The variable `appt-audible' controls the audible reminder."
3536dea8
GM
219 (if appt-audible (beep 1))
220 (cond ((eq appt-display-format 'window)
221 (funcall appt-disp-window-function
222 (number-to-string mins)
223 ;; TODO - use calendar-month-abbrev-array rather than %b?
224 (format-time-string "%a %b %e " (current-time))
225 string)
226 (run-at-time (format "%d sec" appt-display-duration)
227 nil
228 appt-delete-window-function))
229 ((eq appt-display-format 'echo)
230 (message "%s" string))))
8d638c1b
GM
231
232
233(defun appt-check (&optional force)
234 "Check for an appointment and update any reminder display.
235If optional argument FORCE is non-nil, reparse the diary file for
236appointments. Otherwise the diary file is only parsed once per day,
08151ec5 237or when it (or a file it includes) is saved.
902a0e3c 238
8d638c1b
GM
239Note: the time must be the first thing in the line in the diary
240for a warning to be issued. The format of the time can be either
24124 hour or am/pm. For example:
902a0e3c 242
8d638c1b
GM
243 02/23/89
244 18:00 Dinner
a1506d29 245
902a0e3c
JB
246 Thursday
247 11:45am Lunch meeting.
248
f3e7c0dc
KH
249Appointments are checked every `appt-display-interval' minutes.
250The following variables control appointment notification:
902a0e3c 251
8d638c1b
GM
252`appt-display-format'
253 Controls the format in which reminders are displayed.
902a0e3c 254
efa434d9 255`appt-audible'
3952e9d8 256 Non-nil means there is an audible component to reminders.
902a0e3c 257
8d638c1b 258`appt-message-warning-time'
3952e9d8
GM
259 The default number of minutes in advance at which reminders
260 should start.
8d638c1b
GM
261
262`appt-display-mode-line'
3952e9d8
GM
263 Non-nil means show in the mode line a countdown to the
264 time of each appointment, once reminders start.
8d638c1b
GM
265
266`appt-display-interval'
3952e9d8 267 Interval in minutes at which to display appointment messages.
902a0e3c 268
8d638c1b 269`appt-display-diary'
3952e9d8
GM
270 Non-nil means display the diary whenever the appointment list is
271 initialized (e.g. the first time we check for appointments each day).
8d638c1b
GM
272
273The following variables are only relevant if reminders are being
274displayed in a window:
902a0e3c 275
efa434d9 276`appt-display-duration'
3952e9d8 277 Number of seconds for which an appointment message is displayed.
b570e652 278
f3e7c0dc 279`appt-disp-window-function'
debf91fd 280 Function called to display appointment window.
a1506d29 281
f3e7c0dc 282`appt-delete-window-function'
debf91fd 283 Function called to remove appointment window and buffer."
ce5b3019 284 (interactive "P") ; so people can force updates
f3e7c0dc 285 (let* ((min-to-app -1)
debf91fd 286 (prev-appt-mode-string appt-mode-string)
3952e9d8 287 (prev-appt-display-count appt-display-count)
4691905a 288 now now-mins appt-mins appt-warn-time)
98dc3df3
GM
289 (save-excursion ; FIXME ?
290 ;; Convert current time to minutes after midnight (12.01am = 1).
291 (setq now (decode-time)
4691905a 292 now-mins (+ (* 60 (nth 2 now)) (nth 1 now)))
98dc3df3
GM
293 ;; At first check in any day, update appointments to today's list.
294 (if (or force ; eg initialize, diary save
295 (null appt-prev-comp-time) ; first check
4691905a 296 (< now-mins appt-prev-comp-time)) ; new day
98dc3df3
GM
297 (ignore-errors
298 (let ((diary-hook (if (assoc 'appt-make-list diary-hook)
299 diary-hook
300 (cons 'appt-make-list diary-hook))))
301 (if appt-display-diary
302 (diary)
303 ;; Not displaying the diary, so we can ignore
304 ;; diary-number-of-entries. Since appt.el only
305 ;; works on a daily basis, no need for more entries.
306 (diary-list-entries (calendar-current-date) 1 t)))))
3952e9d8
GM
307 ;; Reset everything now in case we somehow missed a minute,
308 ;; or (more likely) an appt was deleted. (This is the only
309 ;; reason we need prev-appt-display-count.)
4691905a 310 (setq appt-prev-comp-time now-mins
98dc3df3 311 appt-mode-string nil
3952e9d8 312 appt-display-count 0)
4691905a
GM
313 ;; Remove any entries that are in the past.
314 ;; FIXME how can there be any such entries, given that this
315 ;; function removes entries when they hit zero minutes,
316 ;; and appt-make-list doesn't add any in the past in the first place?
317 (while (and appt-time-msg-list
318 (< (setq appt-mins (caar (car appt-time-msg-list)))
319 now-mins))
320 (setq appt-time-msg-list (cdr appt-time-msg-list)))
98dc3df3
GM
321 ;; If there are entries in the list, and the user wants a
322 ;; message issued, get the first time off of the list and
323 ;; calculate the number of minutes until the appointment.
324 (when appt-time-msg-list
4691905a 325 (setq appt-warn-time (or (nth 3 (car appt-time-msg-list))
98dc3df3 326 appt-message-warning-time)
4691905a 327 min-to-app (- appt-mins now-mins))
98dc3df3
GM
328 ;; If we have an appointment between midnight and
329 ;; `appt-warn-time' minutes after midnight, we
330 ;; must begin to issue a message before midnight. Midnight
331 ;; is considered 0 minutes and 11:59pm is 1439
332 ;; minutes. Therefore we must recalculate the minutes to
333 ;; appointment variable. It is equal to the number of
334 ;; minutes before midnight plus the number of minutes after
335 ;; midnight our appointment is.
4691905a
GM
336 ;; FIXME but appt-make-list constructs appt-time-msg-list to only
337 ;; contain entries with today's date, so this cannot work?
338 ;; Also above we just removed anything with appt-mins < now-mins.
339 (if (and (< appt-mins appt-warn-time)
340 (> (+ now-mins appt-warn-time) appt-max-time))
341 (setq min-to-app (+ (- (1+ appt-max-time) now-mins)
342 appt-mins)))
98dc3df3
GM
343 ;; Issue warning if the appointment time is within
344 ;; appt-message-warning time.
345 (when (and (<= min-to-app appt-warn-time)
346 (>= min-to-app 0))
98dc3df3
GM
347 ;; This is true every appt-display-interval minutes.
348 (and (zerop (mod prev-appt-display-count appt-display-interval))
349 (appt-display-message (cadr (car appt-time-msg-list))
350 min-to-app))
351 (when appt-display-mode-line
352 (setq appt-mode-string
353 (concat " " (propertize
a5464014
GM
354 (format "App't %s"
355 (if (zerop min-to-app) "NOW"
356 (format "in %s min." min-to-app)))
98dc3df3
GM
357 'face 'mode-line-emphasis))))
358 ;; When an appointment is reached, delete it from the
359 ;; list. Reset the count to 0 in case we display another
360 ;; appointment on the next cycle.
361 (if (zerop min-to-app)
362 (setq appt-time-msg-list (cdr appt-time-msg-list)
3952e9d8 363 appt-display-count 0)
4691905a 364 (setq appt-display-count (1+ prev-appt-display-count)))))
98dc3df3
GM
365 ;; If we have changed the mode line string, redisplay all mode lines.
366 (and appt-display-mode-line
4691905a 367 (not (string-equal appt-mode-string prev-appt-mode-string))
98dc3df3
GM
368 (progn
369 (force-mode-line-update t)
370 ;; If the string now has a notification, redisplay right now.
371 (if appt-mode-string
372 (sit-for 0)))))))
902a0e3c 373
902a0e3c 374(defun appt-disp-window (min-to-app new-time appt-msg)
754c5007
GM
375 "Display appointment due in MIN-TO-APP (a string) minutes.
376NEW-TIME is a string giving the date. Displays the appointment
377message APPT-MSG in a separate buffer."
8d638c1b 378 (let ((this-window (selected-window))
bc5777c1
MR
379 (appt-disp-buf (get-buffer-create appt-buffer-name)))
380 ;; Make sure we're not in the minibuffer before splitting the window.
381 ;; FIXME this seems needlessly complicated?
382 (when (minibufferp)
383 (other-window 1)
384 (and (minibufferp) (display-multi-frame-p) (other-frame 1)))
d5b22d88 385 (if (cdr (assq 'unsplittable (frame-parameters)))
debf91fd 386 ;; In an unsplittable frame, use something somewhere else.
0836e2c3
MR
387 (progn
388 (set-buffer appt-disp-buf)
389 (display-buffer appt-disp-buf))
058961dd 390 (unless (or (special-display-p (buffer-name appt-disp-buf))
debf91fd
GM
391 (same-window-p (buffer-name appt-disp-buf)))
392 ;; By default, split the bottom window and use the lower part.
393 (appt-select-lowest-window)
a291c1b7
GM
394 ;; Split the window, unless it's too small to do so.
395 (when (>= (window-height) (* 2 window-min-height))
396 (select-window (split-window))))
3b316424 397 (switch-to-buffer appt-disp-buf))
ce5b3019 398 ;; FIXME Link to diary entry?
3b316424 399 (calendar-set-mode-line
ce5b3019
GM
400 (format " Appointment %s. %s "
401 (if (string-equal "0" min-to-app) "now"
402 (format "in %s minute%s" min-to-app
403 (if (string-equal "1" min-to-app) "" "s")))
404 new-time))
405 (setq buffer-read-only nil
406 buffer-undo-list t)
efa434d9 407 (erase-buffer)
0e13751e 408 (insert appt-msg)
d5b22d88 409 (shrink-window-if-larger-than-buffer (get-buffer-window appt-disp-buf t))
5b586155 410 (set-buffer-modified-p nil)
ce5b3019 411 (setq buffer-read-only t)
725ec4bc 412 (raise-frame (selected-frame))
8d638c1b 413 (select-window this-window)))
a1506d29 414
5b586155
RS
415(defun appt-delete-window ()
416 "Function called to undisplay appointment messages.
417Usually just deletes the appointment buffer."
95cdbff5
RS
418 (let ((window (get-buffer-window appt-buffer-name t)))
419 (and window
debf91fd
GM
420 (or (eq window (frame-root-window (window-frame window)))
421 (delete-window window))))
5b586155
RS
422 (kill-buffer appt-buffer-name)
423 (if appt-audible
424 (beep 1)))
902a0e3c 425
902a0e3c 426(defun appt-select-lowest-window ()
debf91fd 427 "Select the lowest window on the frame."
7c0d9b89 428 (let ((lowest-window (selected-window))
debf91fd 429 (bottom-edge (nth 3 (window-edges)))
ce5b3019 430 next-bottom-edge)
7c0d9b89 431 (walk-windows (lambda (w)
debf91fd
GM
432 (when (< bottom-edge (setq next-bottom-edge
433 (nth 3 (window-edges w))))
434 (setq bottom-edge next-bottom-edge
435 lowest-window w))) 'nomini)
7c0d9b89 436 (select-window lowest-window)))
902a0e3c 437
0cb7f2c0
SM
438(defconst appt-time-regexp
439 "[0-9]?[0-9]\\(h\\([0-9][0-9]\\)?\\|[:.][0-9][0-9]\\)\\(am\\|pm\\)?")
440
f3e7c0dc 441;;;###autoload
d7cd4abb
GM
442(defun appt-add (time msg &optional warntime)
443 "Add an appointment for today at TIME with message MSG.
444The time should be in either 24 hour format or am/pm format.
445Optional argument WARNTIME is an integer (or string) giving the number
446of minutes before the appointment at which to start warning.
447The default is `appt-message-warning-time'."
a675c749 448 (interactive "sTime (hh:mm[am/pm]): \nsMessage:
d7cd4abb
GM
449sMinutes before the appointment to start warning: ")
450 (unless (string-match appt-time-regexp time)
902a0e3c 451 (error "Unacceptable time-string"))
d7cd4abb
GM
452 (and (stringp warntime)
453 (setq warntime (unless (string-equal warntime "")
454 (string-to-number warntime))))
455 (and warntime
456 (not (integerp warntime))
457 (error "Argument WARNTIME must be an integer, or nil"))
b4593555 458 (or appt-timer (appt-activate))
d7cd4abb
GM
459 (let ((time-msg (list (list (appt-convert-time time))
460 (concat time " " msg) t)))
461 ;; It is presently non-sensical to have multiple warnings about
462 ;; the same appointment with just different delays, but it might
463 ;; not always be so. TODO
464 (if warntime (setq time-msg (append time-msg (list warntime))))
74139994
RW
465 (unless (member time-msg appt-time-msg-list)
466 (setq appt-time-msg-list
467 (appt-sort-list (nconc appt-time-msg-list (list time-msg)))))))
902a0e3c
JB
468
469(defun appt-delete ()
470 "Delete an appointment from the list of appointments."
471 (interactive)
8d638c1b 472 (let ((tmp-msg-list appt-time-msg-list))
ce5b3019
GM
473 (dolist (element tmp-msg-list)
474 (if (y-or-n-p (concat "Delete "
475 ;; We want to quote any doublequotes in the
476 ;; string, as well as put doublequotes around it.
477 (prin1-to-string
478 (substring-no-properties (cadr element) 0))
479 " from list? "))
480 (setq appt-time-msg-list (delq element appt-time-msg-list)))))
481 (appt-check)
482 (message ""))
a1506d29 483
902a0e3c 484
11361a8b
SM
485(defvar number)
486(defvar original-date)
487(defvar diary-entries-list)
3536dea8 488
902a0e3c 489(defun appt-make-list ()
5fac723a 490 "Update the appointments list from today's diary buffer.
d073fa5b 491The time must be at the beginning of a line for it to be
8d638c1b
GM
492put in the appointments list (see examples in documentation of
493the function `appt-check'). We assume that the variables DATE and
59b79385 494NUMBER hold the arguments that `diary-list-entries' received.
5fac723a
RS
495They specify the range of dates that the diary is being processed for.
496
3536dea8
GM
497Any appointments made with `appt-add' are not affected by this function."
498 ;; We have something to do if the range of dates that the diary is
499 ;; considering includes the current date.
500 (if (and (not (calendar-date-compare
501 (list (calendar-current-date))
502 (list original-date)))
503 (calendar-date-compare
504 (list (calendar-current-date))
505 (list (calendar-gregorian-from-absolute
506 (+ (calendar-absolute-from-gregorian original-date)
507 number)))))
508 (save-excursion
509 ;; Clear the appointments list, then fill it in from the diary.
510 (dolist (elt appt-time-msg-list)
511 ;; Delete any entries that were not made with appt-add.
512 (unless (nth 2 elt)
513 (setq appt-time-msg-list
514 (delq elt appt-time-msg-list))))
515 (if diary-entries-list
516 ;; Cycle through the entry-list (diary-entries-list)
517 ;; looking for entries beginning with a time. If the
518 ;; entry begins with a time, add it to the
519 ;; appt-time-msg-list. Then sort the list.
520 (let ((entry-list diary-entries-list)
5006e634 521 time-string literal)
3536dea8
GM
522 ;; Below, we assume diary-entries-list was in date
523 ;; order. It is, unless something on
524 ;; diary-list-entries-hook has changed it, eg
525 ;; diary-include-other-files (bug#7019). It must be
526 ;; in date order if number = 1.
527 (and diary-list-entries-hook
528 appt-display-diary
529 (not (eq diary-number-of-entries 1))
530 (not (memq (car (last diary-list-entries-hook))
531 '(diary-sort-entries sort-diary-entries)))
532 (setq entry-list (sort entry-list 'diary-entry-compare)))
533 ;; Skip diary entries for dates before today.
534 (while (and entry-list
535 (calendar-date-compare
536 (car entry-list) (list (calendar-current-date))))
537 (setq entry-list (cdr entry-list)))
538 ;; Parse the entries for today.
539 (while (and entry-list
540 (calendar-date-equal
541 (calendar-current-date) (caar entry-list)))
5006e634
GM
542 (setq time-string (cadr (car entry-list))
543 ;; Including any comments.
544 literal (or (nth 2 (nth 3 (car entry-list)))
545 time-string))
3536dea8
GM
546 (while (string-match appt-time-regexp time-string)
547 (let* ((beg (match-beginning 0))
548 ;; Get just the time for this appointment.
549 (only-time (match-string 0 time-string))
550 ;; Find the end of this appointment
551 ;; (the start of the next).
552 (end (string-match
553 (concat "\n[ \t]*" appt-time-regexp)
554 time-string
555 (match-end 0)))
5006e634
GM
556 (warntime
557 (if (string-match appt-warning-time-regexp literal)
558 (string-to-number (match-string 1 literal))))
3536dea8
GM
559 ;; Get the whole string for this appointment.
560 (appt-time-string
561 (substring time-string beg end))
4accbca6
GM
562 ;; FIXME why the list? It makes the first
563 ;; element (MINUTES) rather than MINUTES.
3536dea8 564 (appt-time (list (appt-convert-time only-time)))
5006e634
GM
565 (time-msg (append
566 (list appt-time appt-time-string)
567 (if warntime (list nil warntime)))))
3536dea8
GM
568 ;; Add this appointment to appt-time-msg-list.
569 (setq appt-time-msg-list
570 (nconc appt-time-msg-list (list time-msg))
571 ;; Discard this appointment from the string.
5006e634 572 ;; (This allows for multiple appts per entry.)
3536dea8 573 time-string
5006e634
GM
574 (if end (substring time-string end) ""))
575 ;; Similarly, discard the start of literal.
576 (and (> (length time-string) 0)
577 (string-match appt-time-regexp literal)
578 (setq end (string-match
579 (concat "\n[ \t]*" appt-time-regexp)
580 literal (match-end 0)))
581 (setq literal (substring literal end)))))
3536dea8
GM
582 (setq entry-list (cdr entry-list)))))
583 (setq appt-time-msg-list (appt-sort-list appt-time-msg-list))
584 ;; Convert current time to minutes after midnight (12:01am = 1),
5233edd7 585 ;; and remove elements in the list that are in the past.
3536dea8 586 (let* ((now (decode-time))
5233edd7
GM
587 (now-mins (+ (* 60 (nth 2 now)) (nth 1 now))))
588 (while (and appt-time-msg-list
589 (< (caar (car appt-time-msg-list)) now-mins))
590 (setq appt-time-msg-list (cdr appt-time-msg-list)))))))
a1506d29 591
902a0e3c 592
902a0e3c 593(defun appt-sort-list (appt-list)
8d638c1b
GM
594 "Sort an appointment list, putting earlier items at the front.
595APPT-LIST is a list of the same format as `appt-time-msg-list'."
11361a8b 596 (sort appt-list (lambda (e1 e2) (< (caar e1) (caar e2)))))
902a0e3c
JB
597
598
599(defun appt-convert-time (time2conv)
754c5007 600 "Convert hour:min[am/pm] format TIME2CONV to minutes from midnight.
8d638c1b
GM
601A period (.) can be used instead of a colon (:) to separate the
602hour and minute parts."
0cb7f2c0
SM
603 ;; Formats that should be accepted:
604 ;; 10:00 10.00 10h00 10h 10am 10:00am 10.00am
605 (let ((min (if (string-match "[h:.]\\([0-9][0-9]\\)" time2conv)
606 (string-to-number (match-string 1 time2conv))
607 0))
608 (hr (if (string-match "[0-9]*[0-9]" time2conv)
609 (string-to-number (match-string 0 time2conv))
610 0)))
cb17eeae 611 ;; Convert the time appointment time into 24 hour time.
85bbde63 612 (cond ((and (string-match "pm" time2conv) (< hr 12))
debf91fd
GM
613 (setq hr (+ 12 hr)))
614 ((and (string-match "am" time2conv) (= hr 12))
85bbde63 615 (setq hr 0)))
cb17eeae 616 ;; Convert the actual time into minutes.
0cb7f2c0 617 (+ (* hr 60) min)))
902a0e3c 618
8d638c1b
GM
619(defun appt-update-list ()
620 "If the current buffer is visiting the diary, update appointments.
92b99a01
GM
621This function also acts on any file listed in `diary-included-files'.
622It is intended for use with `write-file-functions'."
623 (and (member buffer-file-name (append diary-included-files
624 (list (expand-file-name diary-file))))
8d638c1b
GM
625 appt-timer
626 (let ((appt-display-diary nil))
627 (appt-check t)))
628 nil)
629
8d638c1b
GM
630;;;###autoload
631(defun appt-activate (&optional arg)
debf91fd 632 "Toggle checking of appointments.
8d638c1b
GM
633With optional numeric argument ARG, turn appointment checking on if
634ARG is positive, otherwise off."
635 (interactive "P")
636 (let ((appt-active appt-timer))
637 (setq appt-active (if arg (> (prefix-numeric-value arg) 0)
638 (not appt-active)))
639 (remove-hook 'write-file-functions 'appt-update-list)
640 (or global-mode-string (setq global-mode-string '("")))
641 (delq 'appt-mode-string global-mode-string)
a19de628
GM
642 (when appt-timer
643 (cancel-timer appt-timer)
644 (setq appt-timer nil))
b4593555
GM
645 (if appt-active
646 (progn
b4593555
GM
647 (add-hook 'write-file-functions 'appt-update-list)
648 (setq appt-timer (run-at-time t 60 'appt-check)
649 global-mode-string
650 (append global-mode-string '(appt-mode-string)))
651 (appt-check t)
7e1e2a6e
GM
652 (message "Appointment reminders enabled%s"
653 ;; Someone might want to use appt-add without a diary.
654 (if (ignore-errors (diary-check-diary-file))
655 ""
656 " (no diary file found)")))
b4593555 657 (message "Appointment reminders disabled"))))
8d638c1b 658
efa434d9 659
8d638c1b 660(provide 'appt)
5b586155 661
efa434d9 662;;; appt.el ends here