Update.
[elisp.git] / enotes.el
1 ;; -*-Emacs-Lisp-*-
2
3 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
4 ;; This program is free software; you can redistribute it and/or         ;;
5 ;; modify it under the terms of the GNU General Public License as        ;;
6 ;; published by the Free Software Foundation; either version 3, or (at   ;;
7 ;; your option) any later version.                                       ;;
8 ;;                                                                       ;;
9 ;; This program is distributed in the hope that it will be useful, but   ;;
10 ;; WITHOUT ANY WARRANTY; without even the implied warranty of            ;;
11 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU      ;;
12 ;; General Public License for more details.                              ;;
13 ;;                                                                       ;;
14 ;; You should have received a copy of the GNU General Public License     ;;
15 ;; along with this program. If not, see <http://www.gnu.org/licenses/>.  ;;
16 ;;                                                                       ;;
17 ;; Written by and Copyright (C) Francois Fleuret                         ;;
18 ;; Contact <francois@fleuret.org> for comments & bug reports             ;;
19 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
20
21 ;; This file contains functions to handle rendez-vous and
22 ;; appointments. It has a list of 'notes', each defined by a date, a
23 ;; title, a warning date and optionnaly a tag color and a string of
24 ;; information. The system automatically opens a window when an alarm
25 ;; has to be displayed.
26
27 ;; Just call enotes/init to load the notes saved during the last
28 ;; session and run the whole stuff. The notes are kept in the variable
29 ;; enotes/notes and saved when a note is added or when emacs is
30 ;; killed.
31
32 ;; You can bring the main buffer containing all notes by calling
33 ;; enotes/show-all-notes. The defined keys are given at the top of
34 ;; that buffer.
35
36 ;; I use the following in my .emacs
37 ;;
38 ;; ;; Load the script itself
39 ;; (load "enotes")
40 ;; ;; Load the notes and display the required alarms
41 ;; (enotes/init)
42 ;; ;; That short-cuts to edit all the notes
43 ;; (define-key global-map [(control x) (control n)] 'enotes/show-all-notes)
44 ;;
45 ;; Check the defcustom in the source below to see the tunable
46 ;; variables.
47
48 (eval-when-compile (require 'cl))
49
50 (require 'time-date)
51 (require 'parse-time)
52
53 (defgroup enotes ()
54   "Set of functions to handle notes and rendez-vous."
55   :version "1.3.1")
56
57 (provide 'enotes)
58
59 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
60
61 (defcustom enotes/file "~/.enotes"
62   "File containing the list of notes."
63   :type 'string
64   :group 'enotes)
65
66 (defcustom enotes/alarm-hook nil
67   "Hook called when alarms are to be displayed."
68   :type 'hook
69   :group 'enotes)
70
71 (defcustom enotes/input-time-format "%Y %b %d %H:%M"
72   "The time format for input."
73   :type 'string
74   :group 'enotes)
75
76 (defcustom enotes/time-format "%h %a %d %Y %H:%M"
77   "The time format."
78   :type 'string
79   :group 'enotes)
80
81 (defcustom enotes/show-help t
82   "Should the key help be displayed."
83   :type 'boolean
84   :group 'enotes)
85
86 (defcustom enotes/full-display t
87   "Should the infos be displayed."
88   :type 'boolean
89   :group 'enotes)
90
91 (defcustom enotes/display-mode 'enotes/insert-all-notes-by-week
92   "How to show the notes. Either `enotes/insert-all-notes-by-delay' or
93 `enotes/insert-all-notes-by-week'."
94   :type 'function
95   :group 'enotes)
96
97 (defcustom enotes/color-list '("red" "green3" "yellow" "blue")
98   "What colors can be given to the tags in front of the note titles"
99   :type 'list
100   :group 'enotes)
101
102 (defcustom enotes/default-time-fields "6:00"
103   "The default values for non-specified time fields when setting a date."
104   :type 'string
105   :group 'enotes)
106
107 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
108
109 (defconst enotes/help-string "   n,TAB: go to next note       p,S-TAB: go to prev note
110    a: add note                  C-d,DEL: delete note
111    e: edit field at point       c: change tag color
112    d: edit event time           w: edit warning time
113    i: edit information          I: switch full display
114    +: move event +1h            =: move warning +1h
115    T: move event +24h           t: move warning +24h
116    f: force warning time at event time
117    h: show/hide help            m: switch display mode
118    u: undo                      r: redo
119    s: save notes                RET,g: go to reference
120    q: quit                      Q: remove obsolete notes and quit
121
122    Contact <francois@fleuret.org> for remarks & bug reports.")
123
124 (defmacro enotes/get-event-time (note) `(elt ,note 0))
125 (defmacro enotes/get-warning-time (note) `(elt ,note 1))
126 (defmacro enotes/get-note-time (note) `(elt ,note 2))
127 (defmacro enotes/get-title (note) `(elt ,note 3))
128 (defmacro enotes/get-ref (note) `(elt ,note 4))
129 (defmacro enotes/get-info (note) `(elt ,note 5))
130 (defmacro enotes/get-color (note) `(elt ,note 6))
131
132 (defun enotes/set-event-time (note date) (aset note 0 date))
133 (defun enotes/set-warning-time (note date) (aset note 1 date))
134 (defun enotes/set-note-time (note date) (aset note 2 date))
135 (defun enotes/set-title (note title) (aset note 3 (if (string= title "") "(No title)" title)))
136 (defun enotes/set-ref (note ref) (aset note 4 ref))
137 (defun enotes/set-info (note info) (aset note 5  (if (string= info "") nil info)))
138 (defun enotes/set-color (note color) (aset note 6  (if (string= color "") nil color)))
139
140 (defvar enotes/notes nil "Contains the list of notes")
141 (defvar enotes/mode-map nil "Mode map for enotes/mode")
142
143 (defvar enotes/past-history nil "Contains the history for undo")
144 (defvar enotes/futur-history nil "Contains the history for redo")
145
146 (defconst enotes/version "1.2" "The version Identifier")
147 (defconst enotes/year-duration 31536000 "How many seconds in a year")
148 (defconst enotes/month-duration 2592000 "How many seconds in a month")
149 (defconst enotes/week-duration 604800 "How many seconds in a week")
150 (defconst enotes/day-duration 86400 "How many seconds in a day")
151
152 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
153 ;; Face definitions
154 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
155
156 (defface enotes/list-title-face
157   '((((background light)) (:foreground "royal blue"))
158     (((background dark)) (:foreground "azure2")))
159   "The face for the list titles.")
160
161 (defface enotes/alarm-face
162   '((((background light)) (:foreground "red3" :bold t))
163     (((background dark)) (:foreground "red" :bold t)))
164   "The face for the alarm titles.")
165
166 (defface enotes/wrong-time-face
167   '((((background light)) (:foreground "red3" :bold t))
168     (((background dark)) (:foreground "red" :bold t)))
169   "The face for time in the past.")
170
171 (defface enotes/wrong-warning-face
172   '((((background light)) (:foreground "orange3" :bold t))
173     (((background dark)) (:foreground "orange" :bold t)))
174   "The face for warning after the event.")
175
176 (defface enotes/title-face
177   '((((background light)) (:underline t)))
178   "The face for event title.")
179
180 (defface enotes/information-face
181   '((((background light)) (:foreground "gray50")))
182   "The face for the additional information.")
183
184 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
185
186 ;; (defun enotes/position-note (note)
187 ;;   "Returns the position of note NOTE in buffer or nil if it can not be
188 ;; found"
189 ;;   (let ((pos (point-min)))
190 ;;     (while (and pos (not (eq note (get-text-property pos 'note))))
191 ;;       (message "pos = %s note = %s" (prin1-to-string pos) (prin1-to-string (get-text-property pos 'note)))
192 ;;       (setq pos (next-single-property-change pos 'note))
193 ;;       )
194 ;;     (if (and pos (eq note (get-text-property pos 'note))) pos nil)))
195
196 (defun enotes/go-to-next-note ()
197   "Move the cursor to the next note in buffer"
198   (interactive)
199   (let ((next (next-single-property-change (point) 'note)))
200     (when (and next
201                (not (get-text-property next 'note)))
202       (setq next (next-single-property-change next 'note)))
203     (unless next
204       (setq next (next-single-property-change (point-min) 'note)))
205     (if next (goto-char next)
206       (goto-char (point-min)))))
207
208 (defun enotes/go-to-prev-note ()
209   "Move the cursor to the previous note in buffer"
210   (interactive)
211   (let ((prev (previous-single-property-change (1- (point)) 'note)))
212     (when (and prev
213                (not (get-text-property prev 'note)))
214       (setq prev (previous-single-property-change prev 'note)))
215     (unless prev
216       (setq prev (previous-single-property-change (point-max) 'note)))
217     (if prev (goto-char prev)
218       (goto-char (point-max)))))
219
220 (defun enotes/go-to-ref-at-point ()
221   "Go to the reference (file only at this moment) of the note at cursor's location"
222   (interactive)
223   (let ((note (get-text-property (point) 'note)))
224     (if (not note) (error "No note at point")
225       (let ((ref (enotes/get-ref note)))
226         (if (not ref) (error "No reference")
227           (cond ((equal (car ref) 'file)
228                  (switch-to-buffer (find-file-noselect (car (cdr ref))))
229                  (goto-char (car (cddr ref))))
230                 (t (error "Unknown attachement"))))))))
231
232 (defun enotes/add-file-note ()
233   "Add a note with a reference to the visited file"
234   (interactive)
235   (let ((date (format-time-string enotes/input-time-format (time-add (current-time) `(0 ,enotes/day-duration 0))))
236         (file (buffer-file-name)))
237     (if (not file)
238         (error "You are not visiting a file")
239       (enotes/add-note date "Going on working" (list 'file file (point))))))
240
241 (defun enotes/round-time (time delay)
242   "Heuristic to round the given time according to how far away it is
243 in the futur"
244   (cond ((> delay enotes/month-duration) (+ 25200 (- time (mod time enotes/day-duration))))
245         ((> delay enotes/day-duration) (- time (mod time 3600)))
246         ((> delay 11400) (- time (mod time 900)))
247         ((> delay 1800) (- time (mod time 300)))
248         ((> delay 300) (- time (mod time 60)))
249         (t (fround time))))
250
251 (defun enotes/next-in-list (x l)
252   (if x
253       (if (equal x (car l)) (car (cdr l))
254         (and l (enotes/next-in-list x (cdr l))))
255     (car l)))
256
257 (defun enotes/next-color ()
258   "Change the color mark of the event at point"
259   (interactive)
260   (let* ((note (get-text-property (point) 'note))
261          (color (and note (enotes/get-color note))))
262     (when note
263       (enotes/store-for-undo)
264       (enotes/set-color note (enotes/next-in-list color enotes/color-list))
265       (enotes/do-it))))
266
267 (defun enotes/move-warning (change)
268   "Move the next warning 24 hours in the futur"
269   (interactive)
270   (let* ((note (get-text-property (point) 'note))
271          (time (float-time))
272          (event-time (enotes/get-event-time note))
273          (warning-time (enotes/get-warning-time note))
274          (new-warning-time (+ change warning-time)))
275     (enotes/store-for-undo)
276     (if (and (< warning-time event-time) (> new-warning-time event-time))
277         (enotes/set-warning-time note event-time)
278       (enotes/set-warning-time note new-warning-time)))
279   (enotes/do-it))
280
281 (defun enotes/move-warning-1h ()
282   "Move the next warning one hour in the futur"
283   (interactive)
284   (enotes/move-warning 3600))
285
286 (defun enotes/move-warning-24h ()
287   "Move the next warning 24 hours in the futur"
288   (interactive)
289   (enotes/move-warning enotes/day-duration))
290
291 (defun enotes/move-event (change)
292   "Move the event date itself"
293   (interactive)
294   (let* ((note (get-text-property (point) 'note))
295          (event-time (and note (enotes/get-event-time note)))
296          (new-event-time (and event-time (+ change event-time))))
297     (when note
298       (enotes/store-for-undo)
299       (enotes/set-event-time note new-event-time)
300       (enotes/set-refresh-warning-time note)
301       (enotes/do-it))))
302
303 (defun enotes/move-event-24h ()
304   "Move the event date itself 24 hours in the futur"
305   (interactive)
306   (enotes/move-event enotes/day-duration))
307
308 (defun enotes/move-event-1h ()
309   "Move the event date itself one hour in the futur"
310   (interactive)
311   (enotes/move-event 3600))
312
313 (defun enotes/set-refresh-warning-time (note)
314   "Compute a new warning date, according to the event date, the note
315 creating date and the current next warning. This is an ad-hoc
316 heuristic. Improvements are welcome"
317
318   (if (enotes/get-warning-time note)
319
320       ;; If it's not the first warning, we compute it as a delay from
321       ;; now
322
323       (let* ((time (float-time))
324              (event-time (enotes/get-event-time note))
325              (warning-time (enotes/get-warning-time note))
326              (note-time (enotes/get-note-time note))
327              (anticipation (- event-time note-time))
328              (delay (- event-time time))
329              (delay-warning
330
331               (cond
332                ((> anticipation enotes/year-duration)
333                 ;; The note was set more than ONE YEAR before the
334                 ;; event (serious stuff!)
335                 (cond ((> delay (* 2 enotes/month-duration)) enotes/month-duration)
336                       ((> delay (* 2 enotes/week-duration)) enotes/week-duration)
337                       (t enotes/day-duration)))
338
339                ((> anticipation enotes/month-duration)
340                 ;; The note was set at least one month before the
341                 ;; event
342                 (cond ((> delay enotes/week-duration) (* 2 enotes/day-duration))
343                       (t enotes/day-duration)))
344
345                ((> anticipation enotes/week-duration)
346                 ;; The note was set at least one week before the event
347                 (cond ((> delay enotes/day-duration) enotes/day-duration)
348                       (t 3600)))
349
350                (t
351                 (cond ((> delay enotes/day-duration) enotes/day-duration)
352                       ((> delay 1800) 1800)
353                       (t 900)))
354
355                ))
356
357              (new-warning-time (enotes/round-time (+ time delay-warning) delay)))
358
359         ;; If the preceding warning was before the event and the new
360         ;; is after, force the new at the event date
361
362         (if (and (< warning-time event-time) (> new-warning-time event-time))
363             (enotes/set-warning-time note event-time)
364           ;; else let the new be where we computed
365           (enotes/set-warning-time note new-warning-time)))
366
367     ;; If it's the first warning, we define how long before the event
368     ;; it has to be set
369
370     (let* ((time (fround (float-time)))
371            (anticipation (- (enotes/get-event-time note) (enotes/get-note-time note)))
372            (delay-warning
373             (cond
374              ((> anticipation enotes/year-duration) (* 2 enotes/month-duration))
375              ((> anticipation enotes/month-duration) enotes/week-duration)
376              ((> anticipation enotes/week-duration) (* 2 enotes/day-duration))
377              ((> anticipation (* 2 enotes/day-duration)) enotes/day-duration)
378              (t 3600)
379              ))
380            (delay-warning (- (- (enotes/get-event-time note) delay-warning) time)))
381
382       ;; Force at least 60s in the future
383
384       (enotes/set-warning-time
385        note
386        (max (+ time 60)
387             (enotes/round-time (+ time delay-warning) delay-warning))))
388     )
389   )
390
391 (defun enotes/add-note (&optional date title ref info)
392   "Add a note and ask for the field values if they are not provided"
393   (interactive)
394
395   (let* ((title (read-from-minibuffer
396                  "Title: "
397                  (or title "")))
398          (date (read-from-minibuffer
399                 "Date: "
400                 (or date
401                     (format-time-string enotes/input-time-format
402                                         (current-time)))))
403          (info "")
404          (new-note (vector (enotes/string-to-float-time date)
405                            nil
406                            (fround (float-time))
407                            nil
408                            ref
409                            (if (string= info "") nil info)
410                            nil)))
411
412     (enotes/set-title new-note title)
413     (enotes/set-refresh-warning-time new-note)
414
415     (enotes/store-for-undo)
416
417     (setq enotes/notes (cons new-note enotes/notes))
418     (enotes/save-notes)
419     (enotes/do-it)
420     ;;     (message "%s (%s)" (prin1-to-string new-note) (prin1-to-string (enotes/position-note new-note)))
421     ))
422
423 (defun enotes/default-list (l default-l)
424   (when l (cons (or (car l) (car default-l))
425                 (enotes/default-list (cdr l) (cdr default-l)))))
426
427 (defun enotes/default-time-fields ()
428   (let ((time (decode-time (current-time))))
429     (enotes/default-list
430      (parse-time-string enotes/default-time-fields)
431      `(0 0 6 1 ,(elt time 4) ,(elt time 5)))
432     ))
433
434 (defun enotes/string-to-float-time (date)
435   (let ((time (decode-time (current-time))))
436     (float-time (apply 'encode-time
437                        (enotes/default-list
438                         (parse-time-string date)
439                         (enotes/default-time-fields))))))
440
441 (defun enotes/second-to-delay (second)
442   "Returns a string describing a delay in english"
443   (cond ((< second (- enotes/day-duration))
444          (format "%d day%s ago"
445                  (/ second -86400)
446                  (if (> (ftruncate (/ second -86400)) 1)
447                      "s" "")))
448         ((< second -3600)
449          (format "%dh ago" (/ second -3600)))
450         ((< second -300)
451          (format "%dmin ago" (/ second -60)))
452         ((< second 0)
453          (format "now!!!" (/ second -60)))
454         ((< second 3600)
455          (format "in %dmin" (/ second 60)))
456         ((< second enotes/day-duration)
457          (format "in %dh" (/ second 3600)))
458         ((< second (* 3 enotes/month-duration))
459          (format "in %d day%s" (/ second 86400)
460                  (if (> (ftruncate (/ second 86400)) 1)
461                      "s" "")))
462         (t
463          (format "in ~ %d month%s" (/ second 2592000)
464                  (if (> (ftruncate (/ second 2592000)) 1)
465                      "s" "")))))
466
467 (defun enotes/cond-propertize (cnd str prop)
468   "Propertize STR if both CND and PROP are non-nil"
469   (if (and prop cnd) (apply 'propertize (cons str prop))
470     str))
471
472 (defun enotes/title-string (note)
473   (concat
474
475    (propertize
476
477     (concat
478      " "
479
480      ;; The small color tag
481
482      (if (enotes/get-color note)
483          (propertize " "
484                      'face (cons 'background-color (enotes/get-color note)))
485        " ")
486
487      (if (equal (enotes/get-event-time note) (enotes/get-warning-time note)) "+" " ")
488
489      " ")
490
491     'field 'title)
492
493    (propertize
494     (enotes/get-title note)
495     'face 'enotes/title-face
496     'field 'title)
497
498    (if (and (not enotes/full-display) (enotes/get-info note)) (propertize " /.../" 'field 'information) "")
499
500    ))
501
502 (defun enotes/insert-blank-line () (interactive)
503   (let ((p (point)))
504     (unless (and
505              (> p 1)
506              (eq (char-before p) ?\n)
507              (or (eq p 2)
508                  (eq (char-before (1- p)) ?\n)))
509       (insert "\n"))))
510
511 (defun enotes/insert-note (note time)
512   "Insert the note in the buffer, with fields properties so that we can
513 edit them easily later on"
514   (let ((obsolete (>= time (enotes/get-event-time note)))
515         (info (enotes/get-info note))
516         (title (enotes/title-string note)))
517
518     (when enotes/full-display (enotes/insert-blank-line))
519
520     (insert
521      (propertize
522       (concat
523
524        ;; Title
525
526        title
527
528        (if enotes/full-display "\n"
529          (make-string (max 0 (- 40 (length title))) ? )
530          )
531
532        ;; Date event
533
534        (propertize
535         (concat
536          (if enotes/full-display "       Date: " "   ")
537          (enotes/cond-propertize
538           obsolete
539           (format-time-string enotes/time-format (seconds-to-time (enotes/get-event-time note)))
540           '(face enotes/wrong-time-face))
541          " ("
542          (enotes/second-to-delay (- (enotes/get-event-time note) time))
543          ")\n")
544         'field 'event-time)
545
546        ;; Date next warning
547
548        (when (and enotes/full-display
549                   (not (equal (enotes/get-warning-time note) (enotes/get-event-time note))))
550          (propertize
551           (concat
552            "    Warning: "
553            (enotes/cond-propertize
554             (and (not obsolete) (> (enotes/get-warning-time note) (enotes/get-event-time note)))
555             (format-time-string enotes/time-format (seconds-to-time (enotes/get-warning-time note)))
556             '(face enotes/wrong-warning-face))
557            "\n"
558            )
559           'field 'warning-time)
560          )
561
562        ;; Reference (if there is one)
563
564        (let ((ref (enotes/get-ref note)))
565          (when ref
566            (cond ((equal 'file (car ref))
567                   (format "        Ref: file [%s]\n" (file-name-nondirectory (car (cdr ref)))))
568                  (t "       Ref: *unknown type*\n"))))
569
570        ;; Complementary information (if there are some)
571
572        (when (and enotes/full-display info)
573          (propertize
574           (format "       Info: %s\n"
575                   (propertize
576                    ;; Ugly hack to match exactly the end of
577                    ;; the string: add a ^_ at the end ...
578                    (replace-regexp-in-string "[\n ]*\1f" ""
579                                              (replace-regexp-in-string "\n\\([^\n]+\\)"
580                                                                        "\n             \\1"
581                                                                        (concat info "\1f")))
582                    'face 'enotes/information-face)
583                   )
584           'field 'information)
585          )
586
587        )
588
589       'note note 'obsolete obsolete))))
590
591 (defun enotes/delete-note-at-point ()
592   "Delete the note at cursor's location"
593   (interactive)
594   (let ((note (get-text-property (point) 'note)))
595     (if (not note) (error "No note at point")
596       (enotes/store-for-undo)
597       (setq enotes/notes (delq note enotes/notes))))
598   (enotes/do-it))
599
600 (defun enotes/set-warning-at-event ()
601   "Force the next warning time at the event time"
602   (interactive)
603   (let ((time (float-time))
604         (note (get-text-property (point) 'note)))
605     (if (not note) (error "No note at point")
606       (let ((obsolete (>= time (enotes/get-event-time note))))
607         (enotes/store-for-undo)
608         (if obsolete
609             (enotes/set-warning-time note (+ time 3600))
610           (enotes/set-warning-time note (enotes/get-event-time note))))
611       (enotes/do-it))))
612
613 (defun enotes/switch-help () (interactive)
614   (setq enotes/show-help (not enotes/show-help))
615   (enotes/do-it))
616
617 (defun enotes/switch-infos-display ()
618   "Switch between displaying and not displaying the warning time
619 and additional information"
620   (interactive)
621   (setq enotes/full-display (not enotes/full-display))
622   (enotes/do-it))
623
624 (defun enotes/switch-display () (interactive)
625
626   (setq enotes/display-mode
627         (cdr (assoc
628               enotes/display-mode
629               '((enotes/insert-all-notes-by-delay . enotes/insert-all-notes-by-week)
630                 (enotes/insert-all-notes-by-week . enotes/insert-all-notes-by-delay)))))
631
632   (enotes/do-it))
633
634 (defun enotes/save-note-information () (interactive)
635         (enotes/store-for-undo)
636         (enotes/set-info enotes/edited-note
637                          (buffer-substring-no-properties (point-min)
638                                                          (point-max)))
639         (kill-this-buffer)
640         (enotes/do-it))
641
642 (defun enotes/cancel-edit-info () (interactive)
643   (if (and (buffer-modified-p)
644            (not (y-or-n-p "Lose changes ? ")))
645       (error "Cancel cancel"))
646
647   (kill-this-buffer)
648   (enotes/do-it)
649   (message "Cancel")
650   )
651
652 (defun enotes/edit-information-note-at-point ()
653   "Use the 'field property of the character at point to figure out
654 what note has to have its information edited, and edit it in a new
655 buffer"
656
657   (interactive)
658   (let ((note (get-text-property (point) 'note))
659         (map (make-sparse-keymap)))
660
661     (unless note (error "No note at point"))
662
663     (switch-to-buffer (get-buffer-create
664                        (generate-new-buffer-name "*enotes information*")))
665
666     (text-mode)
667     (auto-fill-mode)
668
669     (define-key map [(control c) (control c)] 'enotes/save-note-information)
670     (define-key map [(control c) (control q)] 'enotes/cancel-edit-info)
671
672     (set (make-local-variable 'enotes/edited-note) note)
673     (set (make-local-variable 'fill-column) 60)
674
675     (use-local-map map)
676     (when (enotes/get-info note)
677       (insert (enotes/get-info note))
678       (setq buffer-undo-list nil)
679       (set-buffer-modified-p nil)
680       (set-auto-mode))
681
682     (message "C-c C-c to save the information, C-c C-q to cancel")
683
684     ))
685
686 (defun enotes/edit-event-time-note-at-point ()
687   (interactive)
688   (let ((note (get-text-property (point) 'note)))
689
690     (unless note (error "No note at point"))
691
692     (let ((new-event-time (enotes/string-to-float-time
693                            (read-from-minibuffer
694                             "Date: "
695                             (format-time-string
696                              enotes/input-time-format
697                              (seconds-to-time (enotes/get-event-time note)))))))
698       (unless (= new-event-time (enotes/get-event-time note))
699         (enotes/store-for-undo)
700         (enotes/set-event-time note new-event-time)
701         (enotes/do-it)))))
702
703 (defun enotes/edit-warning-time-note-at-point ()
704   (interactive)
705   (let ((note (get-text-property (point) 'note)))
706
707     (unless note (error "No note at point"))
708
709     (let ((new-warning-time (enotes/string-to-float-time
710                              (read-from-minibuffer
711                               "Warning: "
712                               (format-time-string
713                                enotes/input-time-format
714                                (seconds-to-time (enotes/get-warning-time note)))))))
715       (unless (= new-warning-time (enotes/get-warning-time note))
716         (enotes/store-for-undo)
717         (enotes/set-warning-time note new-warning-time)
718         (enotes/do-it)))))
719
720 (defun enotes/edit-field-at-point ()
721   "Ask for a new value for the field at cursor's location"
722   (interactive)
723
724   (let ((note (get-text-property (point) 'note))
725         (field (get-text-property (point) 'field)))
726
727     (cond
728
729      ((eq field 'title)
730       (let ((new-title (read-from-minibuffer "Title: " (enotes/get-title note))))
731         (unless (string= new-title (enotes/get-title note))
732           (enotes/store-for-undo)
733           (enotes/set-title note new-title)
734           (enotes/do-it))))
735
736      ((eq field 'event-time)
737       (let ((new-event-time (enotes/string-to-float-time
738                              (read-from-minibuffer
739                               "Date: "
740                               (format-time-string
741                                enotes/input-time-format
742                                (seconds-to-time (enotes/get-event-time note)))))))
743         (unless (= new-event-time (enotes/get-event-time note))
744           (enotes/store-for-undo)
745           (enotes/set-event-time note new-event-time)
746           (enotes/set-refresh-warning-time note)
747           (enotes/do-it))))
748
749      ((eq field 'note-time)
750       (error "Can not edit that field"))
751
752      ((eq field 'warning-time)
753       (let ((new-warning-time (enotes/string-to-float-time
754                                (read-from-minibuffer
755                                 "Warning: "
756                                 (format-time-string
757                                  enotes/input-time-format
758                                  (seconds-to-time (enotes/get-warning-time note)))))))
759         (unless (= new-warning-time (enotes/get-warning-time note))
760           (enotes/store-for-undo)
761           (enotes/set-warning-time note new-warning-time)
762           (enotes/do-it))))
763
764      ((eq field 'information)
765       (enotes/edit-information-note-at-point))
766
767      (t (error "No known field at point"))
768
769      )
770     )
771   )
772
773 (defun enotes/remove-buffer ()
774   "Kill the current buffer and delete the current window if it's not
775 the only one in the frame"
776   (interactive)
777   (kill-this-buffer)
778   (unless (one-window-p t) (delete-window)))
779
780 (defun enotes/remove-obsolete-remove-buffer ()
781   "Delete the obsolete notes appearing in the current buffer, delete
782 the buffer and the current window if it's not the only one in the
783 frame"
784   (interactive)
785
786   (let ((s (point-min)))
787     (while (setq s (text-property-any (1+ s) (point-max) 'obsolete t))
788       (setq enotes/notes (delq (get-text-property s 'note) enotes/notes))))
789
790   ;; If the "list of notes" buffer is visible and is not the current
791   ;; one, refresh it
792
793   (enotes/remove-buffer)
794   (enotes/do-it))
795
796 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
797 ;; The undo/redo stuff
798 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
799
800 (defun enotes/store-for-undo ()
801   "Keep a copy of the current `enotes/notes' in `enotes/past-history'
802 value for undo. Reset `enotes/futur-history' to nil"
803   (interactive)
804   ;; Need to copy the cells themselves, thus the mapcar thingy
805   (setq enotes/past-history (cons (mapcar 'copy-sequence enotes/notes) enotes/past-history)
806         enotes/futur-history nil)
807   )
808
809 (defun enotes/undo ()
810   "Put the current `enotes/notes' into `enotes/futur-history' and take
811 the value of `enotes/notes' from `enotes/past-history'"
812   (interactive)
813   (if (not enotes/past-history)
814       (error "Nothing to undo!")
815     (setq enotes/futur-history (cons enotes/notes enotes/futur-history)
816           enotes/notes (car enotes/past-history)
817           enotes/past-history (cdr enotes/past-history))
818     (enotes/refresh-note-buffer (float-time) t)
819     (message "Undo!"))
820   )
821
822 (defun enotes/redo ()
823   "Put the current `enotes/notes' into `enotes/past-history' and take
824 the value of `enotes/notes' from `enotes/futur-history'"
825   (interactive)
826   (if (not enotes/futur-history)
827       (error "Nothing to redo!")
828     (setq enotes/past-history (cons enotes/notes enotes/past-history)
829           enotes/notes (car enotes/futur-history)
830           enotes/futur-history (cdr enotes/futur-history))
831     (enotes/refresh-note-buffer (float-time) t)
832     (message "Redo!"))
833   )
834
835 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
836
837 (defun enotes/mode ()
838   "Major mode to manage a list of notes. The list of 'notes' is kept
839 in `enotes/notes'. Each note is defined by a date, an event time, a
840 warning time and optionally by a string of informations and a colored
841 tag. Just call `enotes/init' to load the notes saved during the last
842 session and run the whole stuff. The notes are saved when a note is
843 added or when emacs is killed.
844
845 You can bring the main buffer containing all notes by calling
846 `enotes/show-all-notes'."
847
848   (interactive)
849
850   (unless enotes/mode-map
851     (setq enotes/mode-map (make-sparse-keymap))
852     (suppress-keymap enotes/mode-map)
853     (mapc (lambda (x) (define-key enotes/mode-map (car x) (cdr x)))
854           `(([(delete)] . enotes/delete-note-at-point)
855             ([(control d)] . enotes/delete-note-at-point)
856             ("d" . enotes/edit-event-time-note-at-point)
857             ("a" . enotes/add-note)
858             ("e" . enotes/edit-field-at-point)
859             ("h" . enotes/switch-help)
860             ("m" . enotes/switch-display)
861             ("I" . enotes/switch-infos-display)
862             ("i" . enotes/edit-information-note-at-point)
863             ("w" . enotes/edit-warning-time-note-at-point)
864             ("c" . enotes/next-color)
865             ("g" . enotes/go-to-ref-at-point)
866             ("t" . enotes/move-warning-24h)
867             ("T" . enotes/move-event-24h)
868             ("=" . enotes/move-warning-1h)
869             ("+" . enotes/move-event-1h)
870             (,(kbd "RET") . enotes/go-to-ref-at-point)
871             (,(kbd "TAB") . enotes/go-to-next-note)
872             ("n" . enotes/go-to-next-note)
873             ([(shift iso-lefttab)] . enotes/go-to-prev-note)
874             ("p" . enotes/go-to-prev-note)
875             ("q" . enotes/remove-buffer)
876             ("Q" . enotes/remove-obsolete-remove-buffer)
877             ("u" . enotes/undo)
878             ("r" . enotes/redo)
879             ("s" . enotes/save-notes)
880             ([(control x) (control s)] . enotes/save-notes)
881             ("f" . enotes/set-warning-at-event)
882             ))
883
884     (substitute-key-definition 'undo 'enotes/undo enotes/mode-map global-map)
885     )
886
887   (kill-all-local-variables)
888
889   (use-local-map enotes/mode-map)
890
891   (setq mode-name "Enotes"
892         buffer-read-only t
893         ;;         truncate-lines t
894         major-mode 'enotes/mode)
895   )
896
897 (defun enotes/list-of-notes-in-buffer ()
898   "Return all the notes in the current buffer (used to refresh them)"
899   (let ((current (point-min))
900         (result ()))
901     (while (setq current (next-single-property-change current 'note))
902       (when current
903         (let ((n (get-text-property current 'note)))
904           (if (and n (member n enotes/notes)) (setq result (cons n result))))))
905     result))
906
907 (defun enotes/line-title (title)
908   "Create a string of length 75 padded with -s"
909   (concat "-- " title " "
910           (make-string (- 72 (length title)) ?-)
911           ;; "\n"
912           ;; (if enotes/full-display "" "\n")
913           )
914   )
915
916 (defun enotes/sorted-by-time (notes)
917   (sort (copy-sequence notes)
918         (lambda (n1 n2) (and (<= (enotes/get-event-time n1)
919                                  (enotes/get-event-time n2))
920                              (or (not (= (enotes/get-event-time n1)
921                                          (enotes/get-event-time n2)))
922                                  (string< (enotes/get-title n1)
923                                           (enotes/get-title n2)))))))
924
925 ;; Show all notes one after another, sorted by event date. A title is
926 ;; inserted for each week of the year containing events, and for each
927 ;; month.
928
929 (defun enotes/insert-all-notes-by-week (time notes-to-display)
930   "Inserts notes grouped by weeks."
931   (let ((current-week (format-time-string "%W" (seconds-to-time time)))
932         (current-year (format-time-string "%Y" (seconds-to-time time)))
933         (next-week (format-time-string "%W" (seconds-to-time (+ time enotes/week-duration))))
934         (loop-week "")
935         (loop-month "")
936         (loop-year "")
937         (already-added-blank))
938
939     (mapc (lambda (note)
940
941             (let* ((time-event (seconds-to-time (enotes/get-event-time note)))
942                    (week (format-time-string "%W" time-event))
943                    (month (format-time-string "%B" time-event))
944                    (year (format-time-string "%Y" time-event)))
945
946               (when (not (and (string= month loop-month) (string= year loop-year)))
947                 (setq loop-month month
948                       loop-year year)
949                 (insert "\n"
950                         (propertize (enotes/line-title
951                                      (concat month
952                                              (if (not (string= year current-year))
953                                                  (concat " (" year ")"))
954                                              ))
955                                     'face 'enotes/list-title-face)
956                         "\n"
957                         )
958                 (insert "\n")
959                 (setq already-added-blank t)
960                 )
961
962               (when (not (string= week loop-week))
963                 (setq loop-week week)
964                 (unless already-added-blank (insert "\n"))
965                 (insert (propertize (concat "   Week " week
966                                             (when (string= year current-year)
967                                               (if (string= week current-week) " (current)"))
968                                             (when (string= year current-year)
969                                               (if (string= week next-week) " (next week)"))
970                                             "\n")
971                                     'face 'enotes/list-title-face)
972                         )
973
974                 (unless enotes/full-display
975                   (insert "\n")
976                   )
977                 )
978               )
979
980             (setq already-added-blank nil)
981             (enotes/insert-note note time))
982
983           (enotes/sorted-by-time notes-to-display)
984           )
985     ))
986
987 ;; Show all notes one after another, sorted by event date. A title is
988 ;; inserted for "in a day or more", "in a week or more", etc.
989
990 (defun enotes/insert-all-notes-by-delay (time notes-to-display)
991   "Inserts all notes of the current day, then those less than one week
992 in the futur, then those less than one month (30 days) in the futur."
993   (let ((delay 0))
994     (mapc (lambda (note)
995             (let ((s (cond
996                       ((and (< delay enotes/year-duration)
997                             (>= (- (enotes/get-event-time note) time) enotes/year-duration))
998                        (enotes/line-title "In a year or more"))
999
1000                       ((and (< delay enotes/month-duration)
1001                             (>= (- (enotes/get-event-time note) time) enotes/month-duration))
1002                        (enotes/line-title "In a month or more"))
1003
1004                       ((and (< delay enotes/week-duration)
1005                             (>= (- (enotes/get-event-time note) time) enotes/week-duration))
1006                        (enotes/line-title "In a week or more"))
1007
1008                       ((and (< delay enotes/day-duration)
1009                             (>= (- (enotes/get-event-time note) time) enotes/day-duration))
1010                        (enotes/line-title "In a day or more")))))
1011
1012               (when s (insert "\n" (propertize s 'face 'enotes/list-title-face) "\n\n")))
1013
1014             (setq delay (- (enotes/get-event-time note) time))
1015             (enotes/insert-note note time))
1016
1017           (enotes/sorted-by-time notes-to-display)
1018           )
1019     )
1020   )
1021
1022 (defun enotes/refresh-note-buffer (time force-all)
1023
1024   "Refresh the current buffer as the buffer containing the list of
1025 notes. If FORCE-ALL is true display all notes, do not only update
1026 those in the buffer"
1027
1028   ;; This is sort of ugly, we keep track of where we are, to be able
1029   ;; to put back the cursor at the same location (at least the same
1030   ;; note and field, or the position itself), even after massive
1031   ;; modifications
1032
1033   (let ((note (get-text-property (point) 'note))
1034         (field (get-text-property (point) 'field))
1035         (p (point))
1036         (inhibit-read-only t)
1037         (notes-to-display (if force-all enotes/notes (enotes/list-of-notes-in-buffer))))
1038
1039     (erase-buffer)
1040
1041     (when enotes/show-help
1042       (insert "\n"
1043               enotes/help-string "\n")
1044       )
1045
1046     ;; Display all note according to the enotes/display-mode variable.
1047
1048     (if enotes/notes
1049         (eval `(,enotes/display-mode time notes-to-display))
1050       (insert "\n  "
1051               (propertize "No note." 'face 'bold)
1052               " (call enotes/init to load the saved ones).\n"))
1053
1054     (enotes/mode)
1055
1056     ;; Try to go back where we were, if we can't, go to the point
1057     ;; where we were (a priori lame but convenient in practice)
1058
1059     (let* ((s1 (text-property-any (point-min) (point-max) 'note note))
1060            (s2 (and s1 (text-property-any s1 (point-max) 'field field))))
1061       (if s2 (goto-char s2) (goto-char p))
1062       ;;       (recenter)
1063       )
1064     ))
1065
1066 ;; Switches to the note list buffer and refresh it
1067
1068 (defun enotes/show-all-notes (&optional current-window)
1069   "Show all notes in a buffer for edition"
1070   (interactive "P")
1071   (let ((buf (get-buffer "*enotes*")))
1072     (if current-window
1073         (switch-to-buffer (get-buffer-create "*enotes*"))
1074       (switch-to-buffer-other-window (get-buffer-create "*enotes*")))
1075     (enotes/refresh-note-buffer (float-time) t)))
1076
1077 (defun enotes/show-alarms (time)
1078   "Add the new alarms to the alarm buffer"
1079
1080   ;; I have to say, I am not sure to understand what
1081   ;; with-output-to-temp-buffer does ...
1082
1083   (with-output-to-temp-buffer "*enotes alarms*"
1084     (set-buffer "*enotes alarms*")
1085
1086     (insert
1087      "\n"
1088      (propertize
1089       (format "   Alarms (%s)" (format-time-string "%a %b %d %H:%M" (current-time)))
1090       'face 'enotes/alarm-face)
1091      "\n"
1092      )
1093
1094     (when enotes/show-help
1095       (insert "\n"
1096               (propertize (enotes/line-title "Help") 'face 'enotes/list-title-face)
1097               "\n\n" enotes/help-string "\n")
1098       )
1099
1100     (mapc (lambda (note)
1101             (when (>= time (enotes/get-warning-time note))
1102               (enotes/set-refresh-warning-time note)
1103               (enotes/insert-note note time)))
1104           enotes/notes)
1105
1106     (enotes/mode)
1107
1108     (resize-temp-buffer-window))
1109
1110   (run-hooks 'enotes/alarm-hook)
1111   )
1112
1113 (defun enotes/do-it ()
1114
1115   "Refresh all buffers in enotes/mode and forces all notes to be
1116 visible in the main one (called *enotes*). Generates an alarm with the
1117 notes whose warnings are in the past, refresh their warning
1118 times. Sets a call for the soonest one in the future."
1119
1120   (let ((time (float-time)))
1121
1122     ;; Refresh all notes in all enotes buffers
1123     (mapc (lambda (buf)
1124             (set-buffer buf)
1125             (when (eq major-mode 'enotes/mode)
1126               (enotes/refresh-note-buffer time (string= (buffer-name) "*enotes*"))))
1127           (buffer-list))
1128
1129     (setq enotes/notes (sort enotes/notes
1130                              (lambda (n1 n2) (< (enotes/get-warning-time n1)
1131                                                 (enotes/get-warning-time n2)))))
1132
1133     ;; If there is at least one to be shown, show them all
1134     (when (and enotes/notes (>= time (enotes/get-warning-time (car enotes/notes))))
1135       (save-excursion (enotes/show-alarms time)))
1136
1137     ;; If still something in the pipe, set a call for the next time
1138     (when enotes/notes
1139       (run-at-time (1+ (max 0 (- (enotes/get-warning-time (car enotes/notes)) (float-time))))
1140                    nil
1141                    'enotes/do-it))))
1142
1143 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
1144 ;; Saving and loading
1145 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
1146
1147 (defun enotes/remove-properties-in-place (l)
1148   (if (stringp l) (set-text-properties 0 (length l) nil l)
1149     (when (and l (listp l))
1150       (enotes/remove-properties-in-place (car l))
1151       (enotes/remove-properties-in-place (cdr l)))))
1152
1153 (defun enotes/save-notes ()
1154   "Write down in the file specified by `enotes/file' the content of
1155 `enotes/notes'"
1156   (interactive)
1157
1158   ;; There should not be properties in the strings. However, we strip
1159   ;; them out before saving for more safety.
1160
1161   (enotes/remove-properties-in-place enotes/notes)
1162
1163   (with-temp-buffer
1164
1165     ;; We trust the automatic detection of the appropriate encoding
1166     ;; scheme
1167
1168     ;; (set-buffer-file-coding-system 'latin-1)
1169
1170     (set-visited-file-name enotes/file)
1171
1172     (insert ";; -*-Emacs-Lisp-*-\n\n"
1173             ";; Saved by enotes.el on "
1174             (format-time-string "%h %a %d %Y %H:%M:%S" (seconds-to-time (float-time)))
1175             ".\n"
1176             ";; Automatically generated, edit with care.\n"
1177             "\n"
1178             "(setq enotes/notes\n")
1179
1180     (if (not enotes/notes) (insert "()\n")
1181       (insert "'(\n")
1182       ;; We manage to have one note per line, so that it is handled
1183       ;; correctly by CVS & co. (this is slightly messed-up if you
1184       ;; have CRs in the information field)
1185       (mapcar (lambda (entry) (insert (concat (prin1-to-string entry) "\n"))) enotes/notes)
1186       (insert ")\n"))
1187
1188     (insert ")\n")
1189     (emacs-lisp-mode)
1190     (indent-region (point-min) (point-max) nil)
1191     ;; save-buffer ensures the creation of the backup files if
1192     ;; necessary
1193     (save-buffer))
1194
1195   (let ((buf (get-buffer "*enotes*")))
1196     (when buf
1197       (set-buffer buf)
1198       (set-buffer-modified-p nil)))
1199
1200   (message "Notes saved in %s" enotes/file)
1201
1202   )
1203
1204 (defun enotes/load-notes ()
1205
1206   "Load the notes from the file specified by `enotes/file' into `enotes/notes'"
1207
1208   (if (file-exists-p enotes/file)
1209       ;; This hack to handle the old variable name enotes-notes
1210       (let ((enotes-notes nil))
1211         (load enotes/file)
1212         (when (and (not enotes/notes)
1213                    enotes-notes)
1214           (setq enotes/notes enotes-notes)))
1215     (setq enotes/notes ())
1216     (message "Creating a new list of notes. Welcome on board!"))
1217
1218   ;; Fix the length of notes to the current length (i.e. add as many
1219   ;; fields as required to be compliant with the current version)
1220
1221   (setq enotes/notes
1222         (mapcar (lambda (x) ()
1223                   (apply 'vector (append x (make-list (- 7 (length x)) nil))))
1224                 enotes/notes))
1225
1226   ;; If there are events in the past, let's use their date as the
1227   ;; warning-time date
1228
1229   ;;   (mapc (lambda (note)
1230   ;;           (if (> (float-time) (enotes/get-event-time note))
1231   ;;               (enotes/set-event-time note (enotes/get-event-time note))))
1232   ;;         enotes/notes)
1233
1234   (enotes/do-it))
1235
1236 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
1237 ;; The main routine to start all that stuff
1238 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
1239
1240 (defun enotes/init (&optional with-what)
1241   "Loads the notes from the file specified in `enotes/file' and calls
1242 `enotes/do-it'."
1243   (interactive)
1244
1245   (add-hook 'kill-emacs-hook 'enotes/save-notes)
1246
1247   (enotes/load-notes))
1248
1249 ;;   (when (and (memq 'gnus with-what)
1250 ;;              (require 'gnus-sum nil t))
1251
1252 ;;     (defun enotes/add-gnus-note ()
1253 ;;       "Add a note with a reference to a mail"
1254 ;;       (interactive)
1255 ;;       (let ((from
1256 ;;              (save-window-excursion
1257 ;;                (gnus-setup-message 'reply
1258 ;;                  (gnus-summary-select-article)
1259 ;;                  (set-buffer (gnus-copy-article-buffer))
1260 ;;                  (gnus-msg-treat-broken-reply-to))
1261 ;;                (and (re-search-forward "^From: \\(.*\\)$")
1262 ;;                     (match-string-no-properties 1))))
1263 ;;             (date (format-time-string enotes/input-time-format (time-add (current-time) '(0 86400 0)))))
1264 ;;         (when from (enotes/add-note date (concat "Reply to " from)))))
1265 ;;     (define-key enotes/mode-map "m" 'gnus-summary-mail-other-window)
1266 ;;     (define-key 'gnus-summary-mark-map "a" 'enotes/add-gnus-note)
1267 ;;     )
1268
1269 ;;   (when (and (memq 'calendar with-what)
1270 ;;              (require 'parse-time nil t)
1271 ;;              (require 'calendar nil t))
1272
1273 ;;     (defun enotes/show-calendar ()
1274 ;;       (interactive)
1275 ;;       (let ((note (get-text-property (point) 'note)))
1276 ;;         (if (not note) (message "No note at point")
1277 ;;           (calendar-goto-date (format-time-string
1278 ;;                                "%h %a %d %Y %H:%M:%S"
1279 ;;                                (seconds-to-time (enotes/get-event-time note)))))))
1280 ;;     )