changeset 66262:c3855d669e33

* textmodes/org.el (org-combined-agenda-icalendar-file) (org-icalendar-include-todo, org-icalendar-combined-name): New options. (org-export-icalendar-this-file) (org-export-icalendar-all-agenda-files) (org-export-icalendar-combine-agenda-files): New commands. (org-export-icalendar, org-print-icalendar-entries) (org-start-icalendar-file, org-finish-icalendar-file) (org-ical-ts-to-string): New functions. (org-read-date, org-goto-calendar) (org-agenda-goto-calendar): Inhibit displaying diary entries by call to `calendar'. (orgtbl-setup): Removed the :keys arguments from the menu description. (org-after-save-iCalendar-file-hook): New variable.
author Carsten Dominik <dominik@science.uva.nl>
date Fri, 21 Oct 2005 05:55:05 +0000
parents 83a28b92ccd1
children 340d6b4c8e29
files lisp/textmodes/org.el
diffstat 1 files changed, 217 insertions(+), 11 deletions(-) [+]
line wrap: on
line diff
--- a/lisp/textmodes/org.el	Fri Oct 21 01:26:51 2005 +0000
+++ b/lisp/textmodes/org.el	Fri Oct 21 05:55:05 2005 +0000
@@ -5,7 +5,7 @@
 ;; Author: Carsten Dominik <dominik at science dot uva dot nl>
 ;; Keywords: outlines, hypermedia, calendar
 ;; Homepage: http://www.astro.uva.nl/~dominik/Tools/org/
-;; Version: 3.17
+;; Version: 3.18
 ;;
 ;; This file is part of GNU Emacs.
 ;;
@@ -80,6 +80,10 @@
 ;;
 ;; Changes:
 ;; -------
+;; Version 3.18
+;;    - Export of calendar information in the standard iCalendar format.
+;;    - Some bug fixes.
+;;
 ;; Version 3.17
 ;;    - HTML export specifies character set depending on coding-system.
 ;;
@@ -213,7 +217,7 @@
 
 ;;; Customization variables
 
-(defvar org-version "3.17"
+(defvar org-version "3.18"
   "The version number of the file org.el.")
 (defun org-version ()
   (interactive)
@@ -1490,6 +1494,23 @@
   :group 'org-export
   :type 'boolean)
 
+(defcustom org-combined-agenda-icalendar-file "~/org.ics"
+  "The file name for the iCalendar file covering all agenda files.
+This file is created with the command \\[org-export-icalendar-all-agenda-files]."
+  :group 'org-export
+  :type 'file)
+
+(defcustom org-icalendar-include-todo nil
+  "Non-nil means, export to iCalendar files should also cover TODO items."
+  :group 'org-export
+  :type 'boolean)
+
+;; FIXME: not yet used.
+(defcustom org-icalendar-combined-name "OrgMode"
+  "Calendar name for the combined iCalendar representing all agenda files."
+  :group 'org-export
+  :type 'string)
+
 (defgroup org-faces nil
   "Faces for highlighting in Org-mode."
   :tag "Org Faces"
@@ -3179,7 +3200,8 @@
 	;; Copied (with modifications) from planner.el by John Wiegley
 	(save-excursion
 	  (save-window-excursion
-	    (calendar)
+	    (let ((view-diary-entries-initially nil))
+	      (calendar))
 	    (calendar-forward-day (- (time-to-days default-time)
 				     (calendar-absolute-from-gregorian
 				      (calendar-current-date))))
@@ -3523,7 +3545,8 @@
 	      (d2 (time-to-days
 		   (org-time-string-to-time (match-string 1)))))
 	  (setq diff (- d2 d1))))
-    (calendar)
+    (let ((view-diary-entries-initially nil))
+      (calendar))
     (calendar-goto-today)
     (if (and diff (not arg)) (calendar-forward-day diff))))
 
@@ -3628,7 +3651,7 @@
 (define-key org-agenda-mode-map [?\C-c ?\C-x (down)] 'org-agenda-priority-down)
 (define-key org-agenda-mode-map [(right)] 'org-agenda-later)
 (define-key org-agenda-mode-map [(left)] 'org-agenda-earlier)
-
+(define-key org-agenda-mode-map "\C-c\C-x\C-c" 'org-export-icalendar-combine-agenda-files)
 (defvar org-agenda-keymap (copy-keymap org-agenda-mode-map)
   "Local keymap for agenda entries from Org-mode.")
 
@@ -3681,6 +3704,7 @@
      ["Sunrise/Sunset" org-agenda-sunrise-sunset t]
      ["Holidays" org-agenda-holidays t]
      ["Convert" org-agenda-convert-date t])
+    ["Create iCalendar file" org-export-icalendar-combine-agenda-files t]
     "--"
     ["Quit" org-agenda-quit t]
     ["Exit and Release Buffers" org-agenda-exit t]
@@ -4253,6 +4277,9 @@
 
 (defun org-file-menu-entry (file)
   (vector file (list 'find-file file) t))
+;; FIXME: Maybe removed a buffer visited through the menu from
+;; org-agenda-new-buffers, so that the buffer will not be removed
+;; when exiting the agenda????
 
 (defun org-get-all-dates (beg end &optional no-ranges force-today)
   "Return a list of all relevant day numbers from BEG to END buffer positions.
@@ -5222,7 +5249,8 @@
   (let* ((day (or (get-text-property (point) 'day)
 		  (error "Don't know which date to open in calendar")))
 	 (date (calendar-gregorian-from-absolute day)))
-    (calendar)
+    (let ((view-diary-entries-initially nil))
+      (calendar))
     (calendar-goto-date date)))
 
 (defun org-calendar-goto-agenda ()
@@ -8031,10 +8059,10 @@
 	 "--"
 	 ["Insert Hline" org-table-insert-hline :active (org-at-table-p) :keys "C-c -"])
 	("Rectangle"
-	 ["Copy Rectangle" org-copy-special :active (org-at-table-p) :keys "C-c C-x M-w"]
-	 ["Cut Rectangle" org-cut-special :active (org-at-table-p) :keys "C-c C-x C-w"]
-	 ["Paste Rectangle" org-paste-special :active (org-at-table-p) :keys "C-c C-x C-y"]
-	 ["Fill Rectangle" org-table-wrap-region :active (org-at-table-p) :keys "C-c C-q"])
+	 ["Copy Rectangle" org-copy-special :active (org-at-table-p)]
+	 ["Cut Rectangle" org-cut-special :active (org-at-table-p)]
+	 ["Paste Rectangle" org-paste-special :active (org-at-table-p)]
+	 ["Fill Rectangle" org-table-wrap-region :active (org-at-table-p)])
 	"--"
 	["Set Column Formula" org-table-eval-formula :active (org-at-table-p) :keys "C-c ="]
 	["Set Named Field Formula" (org-table-eval-formula '(4)) :active (org-at-table-p) :keys "C-u C-c ="]
@@ -8834,6 +8862,8 @@
 	 (coding-system (and (fboundp 'coding-system-get)
 			     (boundp 'buffer-file-coding-system)
 			     buffer-file-coding-system))
+	 (coding-system-for-write (or coding-system coding-system-for-write))
+	 (save-buffer-coding-system (or coding-system save-buffer-coding-system))
 	 (charset (and coding-system
 		       (coding-system-get coding-system 'mime-charset)))
 	 table-open type
@@ -9066,6 +9096,7 @@
 	(if org-export-html-with-timestamp
 	    (insert org-export-html-html-helper-timestamp))
 	(insert "</body>\n</html>\n")
+	(debug)
 	(normal-mode)
 	(save-buffer)
 	(goto-char (point-min)))))
@@ -9409,6 +9440,172 @@
     string))
 
 
+
+
+
+(defun org-export-icalendar-this-file ()
+  "Export current file as an iCalendar file.
+The iCalendar file will be located in the same directory as the Org-mode
+file, but with extension `.ics'."
+  (interactive)
+  (org-export-icalendar nil (buffer-file-name)))
+
+;;;###autoload
+(defun org-export-icalendar-all-agenda-files ()
+  "Export all files in `org-agenda-files' to iCalendar .ics files.
+Each iCalendar file will be located in the same directory as the Org-mode
+file, but with extension `.ics'."
+  (interactive)
+  (apply 'org-export-icalendar nil org-agenda-files))
+
+;;;###autoload
+(defun org-export-icalendar-combine-agenda-files ()
+  "Export all files in `org-agenda-files' to a single combined iCalendar file.
+The file is stored under the name `org-combined-agenda-icalendar-file'."
+  (interactive)
+  (apply 'org-export-icalendar t org-agenda-files))
+
+(defun org-export-icalendar (combine &rest files)
+  "Create iCalendar files for all elements of FILES.
+If COMBINE is non-nil, combine all calendar entries into a single large
+file and store it under the name `org-combined-agenda-icalendar-file'."
+  (save-excursion
+    (let* (file ical-file ical-buffer category started org-agenda-new-buffers)
+      (when combine
+	(setq ical-file org-combined-agenda-icalendar-file
+	      ical-buffer (org-get-agenda-file-buffer ical-file))
+	(set-buffer ical-buffer) (erase-buffer))
+      (while (setq file (pop files))
+	(catch 'nextfile
+	  (org-check-agenda-file file)
+	  (unless combine
+	    (setq ical-file (concat (file-name-sans-extension file) ".ics"))
+	    (setq ical-buffer (org-get-agenda-file-buffer ical-file))
+	    (set-buffer ical-buffer) (erase-buffer))
+	  (set-buffer (org-get-agenda-file-buffer file))
+	  (setq category (or org-category
+			     (file-name-sans-extension
+			      (file-name-nondirectory (buffer-file-name)))))
+	  (if (symbolp category) (setq category (symbol-name category)))
+	  (let ((standard-output ical-buffer))
+	    (if combine
+		(and (not started) (setq started t)
+		     (org-start-icalendar-file "OrgMode"))
+	      (org-start-icalendar-file category))
+	    (org-print-icalendar-entries combine category)
+	    (when (or (and combine (not files)) (not combine))
+	      (org-finish-icalendar-file)
+	      (set-buffer ical-buffer)
+	      (save-buffer)
+	      (run-hooks 'org-after-save-iCalendar-file-hook)))))
+      (org-release-buffers org-agenda-new-buffers))))
+
+(defvar org-after-save-iCalendar-file-hook nil
+  "Hook run after an iCalendar file has been saved.
+The iCalendar buffer is still current when this hook is run.
+A good way to use this is to tell a desktop calenndar application to re-read
+the iCalendar file.")
+
+(defun org-print-icalendar-entries (&optional combine category)
+  "Print iCalendar entries for the current Org-mode file to `standard-output'.
+When COMBINE is non nil, add the category to each line."
+  (let ((re2 (concat "--?-?\\(" org-ts-regexp "\\)"))
+	(dts (org-ical-ts-to-string
+	      (format-time-string (cdr org-time-stamp-formats) (current-time))
+	      "DTSTART"))
+	hd ts ts2 state (inc t) pos scheduledp deadlinep donep tmp pri)
+    (save-excursion
+      (goto-char (point-min))
+      (while (re-search-forward org-ts-regexp nil t)
+	(setq pos (match-beginning 0)
+	      ts (match-string 0)
+	      inc t
+	      hd (org-get-heading))
+	(if (looking-at re2)
+	    (progn
+	      (goto-char (match-end 0))
+	      (setq ts2 (match-string 1) inc nil))
+	  (setq ts2 ts
+		tmp (buffer-substring (max (point-min)
+					     (- pos org-ds-keyword-length))
+				      pos)
+		deadlinep (string-match org-deadline-regexp tmp)
+		scheduledp (string-match org-scheduled-regexp tmp)
+		donep (org-entry-is-done-p)))
+	(if (or (string-match org-tr-regexp hd)
+		(string-match org-ts-regexp hd))
+	    (setq hd (replace-match "" t t hd)))		
+	(if combine
+	    (setq hd (concat hd " (category " category ")")))
+	(if deadlinep (setq hd (concat "DL: " hd " This is a deadline")))
+	(if scheduledp (setq hd (concat "S: " hd " Scheduled for this date")))
+	(princ (format "BEGIN:VEVENT
+%s
+%s
+SUMMARY:%s
+END:VEVENT\n"
+		       (org-ical-ts-to-string ts "DTSTART")
+		       (org-ical-ts-to-string ts2 "DTEND" inc)
+		       hd)))
+      (when org-icalendar-include-todo
+	(goto-char (point-min))
+	(while (re-search-forward org-todo-line-regexp nil t)
+	  (setq state (match-string 1))
+	  (unless (equal state org-done-string)
+	    (setq hd (match-string 3))
+	    (if (string-match org-priority-regexp hd)
+		(setq pri (string-to-char (match-string 2 hd))
+		      hd (concat (substring hd 0 (match-beginning 1))
+				 (substring hd (- (match-end 1)))))
+	      (setq pri org-default-priority))
+	    (setq pri (floor (1+ (* 8. (/ (float (- org-lowest-priority pri))
+					  (- org-lowest-priority ?A))))))
+
+	    (princ (format "BEGIN:VTODO
+%s
+SUMMARY:%s
+SEQUENCE:1
+PRIORITY:%d
+END:VTODO\n"
+			   dts hd pri))))))))
+
+(defun org-start-icalendar-file (name)
+  "Start an iCalendar file by inserting the header."
+  (let ((user user-full-name)
+	(calname "something")
+	(name (or name "unknown"))
+	(timezone "FIXME"))
+    (princ
+     (format "BEGIN:VCALENDAR
+VERSION:2.0
+X-WR-CALNAME:%s
+PRODID:-//%s//Emacs with Org-mode//EN
+X-WR-TIMEZONE:Europe/Amsterdam
+CALSCALE:GREGORIAN\n" name user timezone))))
+
+(defun org-finish-icalendar-file ()
+  "Finish an iCalendar file by inserting the END statement."
+  (princ "END:VCALENDAR\n"))
+
+(defun org-ical-ts-to-string (s keyword &optional inc)
+  "Take a time string S and convert it to iCalendar format.
+KEYWORD is added in front, to make a complete line like DTSTART....
+When INC is non-nil, increase the hour by two (if time string contains
+a time), or the day by one (if it does not contain a time)."
+  (let ((t1 (org-parse-time-string s 'nodefault))
+	t2 fmt have-time time)
+    (if (and (car t1) (nth 1 t1) (nth 2 t1))
+	(setq t2 t1 have-time t)
+      (setq t2 (org-parse-time-string s)))
+    (let ((s (car t2))   (mi (nth 1 t2)) (h (nth 2 t2))
+	  (d (nth 3 t2)) (m  (nth 4 t2)) (y (nth 5 t2)))
+      (when inc
+	(if have-time (setq h (+ 2 h)) (setq d (1+ d))))
+      (setq time (encode-time s mi h d m y)))
+    (setq fmt (if have-time ":%Y%m%dT%H%M%S" ";VALUE=DATE:%Y%m%d"))
+    (concat keyword (format-time-string fmt time))))
+
+
 ;;; Key bindings
 
 ;; - Bindings in Org-mode map are currently
@@ -9510,9 +9707,13 @@
 (define-key org-mode-map "\C-c\C-x\C-a"   'org-export-as-ascii)
 (define-key org-mode-map "\C-c\C-xv"      'org-export-copy-visible)
 (define-key org-mode-map "\C-c\C-x\C-v"   'org-export-copy-visible)
-;; OPML support is only planned
+;; OPML support is only an option for the future
 ;(define-key org-mode-map "\C-c\C-xo"      'org-export-as-opml)
 ;(define-key org-mode-map "\C-c\C-x\C-o"   'org-export-as-opml)
+(define-key org-mode-map "\C-c\C-xi"      'org-export-icalendar-this-file)
+(define-key org-mode-map "\C-c\C-x\C-i"   'org-export-icalendar-all-agenda-files)
+(define-key org-mode-map "\C-c\C-xc"      'org-export-icalendar-combine-agenda-files)
+(define-key org-mode-map "\C-c\C-x\C-c"   'org-export-icalendar-combine-agenda-files)
 (define-key org-mode-map "\C-c\C-xt"      'org-insert-export-options-template)
 (define-key org-mode-map "\C-c:"          'org-toggle-fixed-width-section)
 (define-key org-mode-map "\C-c\C-xh"      'org-export-as-html)
@@ -9945,6 +10146,11 @@
      ["HTML and Open" org-export-as-html-and-open t]
 ;     ["OPML" org-export-as-opml nil]
      "--"
+     ["iCalendar this file" org-export-icalendar-this-file t]
+     ["iCalendar all agenda files" org-export-icalendar-all-agenda-files
+      :active t :keys "C-c C-x C-i"]
+     ["iCalendar combined" org-export-icalendar-combine-agenda-files t]
+     "--"
      ["Option Template" org-insert-export-options-template t]
      ["Toggle Fixed Width" org-toggle-fixed-width-section t])
     "--"