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