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