Browse Source

Implement Summaries for Column View in agenda.

Carsten Dominik 17 years ago
parent
commit
b0ba028394
5 changed files with 214 additions and 23 deletions
  1. 9 0
      ChangeLog
  2. 57 1
      doc/org.texi
  3. 46 13
      lisp/org-agenda.el
  4. 101 9
      lisp/org-colview.el
  5. 1 0
      lisp/org.el

+ 9 - 0
ChangeLog

@@ -1,5 +1,14 @@
 2008-04-15  Carsten Dominik  <dominik@science.uva.nl>
 
+	* lisp/org-agenda.el (org-agenda-columns-show-summaries)
+	(org-agenda-columns-compute-summary-properties): New options.
+
+	* lisp/org-colview.el (org-agenda-colview-summarize)
+	(org-agenda-colview-compute): New functions.
+	(org-agenda-columns): Call `org-agenda-colview-summarize'.
+
+	* doc/org.texi (Agenda column view): New section.
+
 	* lisp/org.el (org-tbl-menu): Protect the use of variables that
 	are only available when org-table.el gets loaded.
 	(org-read-agenda-file-list): Error if `org-agenda-files' is a

+ 57 - 1
doc/org.texi

@@ -238,6 +238,7 @@ Agenda Views
 * Presentation and sorting::    How agenda items are prepared for display
 * Agenda commands::             Remote editing of Org trees
 * Custom agenda views::         Defining special searches and views
+* Agenda column view::          Using column view for collected entries
 
 The built-in agenda views
 
@@ -4896,6 +4897,7 @@ window configuration is restored when the agenda exits:
 * Presentation and sorting::    How agenda items are prepared for display
 * Agenda commands::             Remote editing of Org trees
 * Custom agenda views::         Defining special searches and views
+* Agenda column view::          Using column view for collected entries
 @end menu
 
 @node Agenda files, Agenda dispatcher, Agenda Views, Agenda Views
@@ -5780,7 +5782,7 @@ visit org files will not be removed.
 @end table
 
 
-@node Custom agenda views,  , Agenda commands, Agenda Views
+@node Custom agenda views, Agenda column view, Agenda commands, Agenda Views
 @section Custom agenda views
 @cindex custom agenda views
 @cindex agenda views, custom
@@ -6203,6 +6205,60 @@ foreach $line (split(/\n/,$agenda)) @{
 @end group
 @end example
 
+@node Agenda column view,  , Custom agenda views, Agenda Views
+@section Using column view in the agenda
+@cindex column view, in agenda
+@cindex agenda, column view
+
+Column view (@pxref{Column view}) is normally used to view and edit
+properties embedded in the hierarchical structure of an Org file.  It can be
+quite useful to use column view also from the agenda, where entries are
+collected by certain criteria.
+
+@table @kbd
+@kindex C-c C-x C-c
+@item C-c C-x C-c
+Turn on column view in the agenda.
+@end table
+
+To understand how to use this properly, it is important to realize that the
+entries in the agenda are no longer in their proper outline environment.
+This causes the following issues:
+
+@enumerate
+@item
+Org needs to make a decision which @code{COLUMNS} format to use.  Since the
+entries in the agenda are collected from different files, and different files
+may have different @code{COLUMNS} formats, this is a non-trivial problem.
+Org first checks if the variable @code{org-overriding-columns-format} is
+currently set, and if yes takes the format from there.  Otherwise it takes
+the format associated with the first item in the agenda, or, if that item
+does not have a specific format (defined in a property, or in it's file), it
+uses @code{org-columns-default-format}.
+@item
+If any of the columns has a summary type defined (@pxref{Column attributes}),
+turning on column view in the agenda will visit all relevant agenda files and
+make sure that the computations of this property are up to date.  This is
+also true for the special @code{CLOCKSUM} property.  Org will then sum the
+values displayed in the agenda.  In the daily/weekly agenda, the sums will
+cover a single day, in all other views they cover the entire block.  It is
+vital to realize that the agenda may show the same entry @emph{twice} (for
+example as scheduled and as a deadline), and it may show two entries from the
+same hierarchy (for example a @emph{parent} and it's @emph{child}).  In these
+cases, the summation in the agenda will lead to incorrect results because
+some values will count double.
+@item
+When the column view in the agenda shows the @code{CLOCKSUM}, that is always
+the entire clocked time for this item.  So even in the daily/weekly agenda,
+the clocksum listed in column view may originate from times outside the
+current view.  This has the advantage that you can compare these values with
+a column listing the planned total effort for a task - one of the major
+applications for column view in the agenda.  If you want information about
+clocked time in the displayed period use clock table mode (press @kbd{R} in
+the agenda).
+@end enumerate
+
+
 @node Embedded LaTeX, Exporting, Agenda Views, Top
 @chapter Embedded LaTeX
 @cindex @TeX{} interpretation

+ 46 - 13
lisp/org-agenda.el

@@ -927,6 +927,28 @@ a names face, or a list like `(:background \"Red\")'."
 			(sexp :tag "face")))))
 
 
+(defgroup org-agenda-column-view nil
+  "Options concerning column view in the agenda."
+  :tag "Org Agenda Column View"
+  :group 'org-agenda)
+
+(defcustom org-agenda-columns-show-summaries t
+  "Non-nil means, show summaries for columns displayed in the agenda view."
+  :group 'org-agenda-column-view
+  :type 'boolean)
+
+(defcustom org-agenda-columns-compute-summary-properties t
+  "Non-nil means, recompute all summary properties before column view.
+When column view in the agenda is listing properties that have a summary
+operator, it can go to all relevant buffers and recompute the summaries
+there.  This can mean overhead for the agenda column view, but is necessary
+to have thing up to date.
+As a special case, a CLOCKSUM property also makes sure that the clock
+computations are current."
+  :group 'org-agenda-column-view
+  :type 'boolean)
+
+
 (eval-when-compile
   (require 'cl))
 (require 'org)
@@ -1858,6 +1880,7 @@ higher priority settings."
 (defvar org-agenda-multi nil)  ; dynammically scoped
 (defvar org-agenda-buffer-name "*Org Agenda*")
 (defvar org-pre-agenda-window-conf nil)
+(defvar org-agenda-columns-active nil)
 (defvar org-agenda-name nil)
 (defun org-prepare-agenda (&optional name)
   (setq org-todo-keywords-for-agenda nil)
@@ -1870,6 +1893,8 @@ higher priority settings."
 	  (insert "\n" (make-string (window-width) ?=) "\n"))
 	(narrow-to-region (point) (point-max)))
     (org-agenda-reset-markers)
+    (setq org-agenda-contributing-files nil)
+    (setq org-agenda-columns-active nil)
     (org-prepare-agenda-buffers (org-agenda-files))
     (setq org-todo-keywords-for-agenda
 	  (org-uniquify org-todo-keywords-for-agenda))
@@ -1891,7 +1916,7 @@ higher priority settings."
 	(delete-other-windows)
 	(org-switch-to-buffer-other-window abuf))))
     (setq buffer-read-only nil)
-    (erase-buffer)
+    (let ((inhibit-read-only t)) (erase-buffer))
     (org-agenda-mode)
     (and name (not org-agenda-name)
 	 (org-set-local 'org-agenda-name name)))
@@ -3503,6 +3528,8 @@ Any match of REMOVE-RE will be removed from TXT."
 	   (ts (if dotime (concat (if (stringp dotime) dotime "") txt)))
 	   (time-of-day (and dotime (org-get-time-of-day ts)))
 	   stamp plain s0 s1 s2 rtn srp)
+      (and (org-mode-p) buffer-file-name
+	   (add-to-list 'org-agenda-contributing-files buffer-file-name))
       (when (and dotime time-of-day org-prefix-has-time)
 	;; Extract starting and ending time and move them to prefix
 	(when (or (setq stamp (string-match org-stamp-time-of-day-regexp ts))
@@ -3854,17 +3881,21 @@ If ERROR is non-nil, throw an error, otherwise just return nil."
 (defun org-agenda-quit ()
   "Exit agenda by removing the window or the buffer."
   (interactive)
-  (let ((buf (current-buffer)))
-    (if (not (one-window-p)) (delete-window))
-    (kill-buffer buf)
-    (org-agenda-reset-markers)
-    (org-columns-remove-overlays))
-  ;; Maybe restore the pre-agenda window configuration.
-  (and org-agenda-restore-windows-after-quit
-       (not (eq org-agenda-window-setup 'other-frame))
-       org-pre-agenda-window-conf
-       (set-window-configuration org-pre-agenda-window-conf)))
-
+  (if org-agenda-columns-active
+      (progn
+	(setq org-agenda-columns-active nil)
+	(org-columns-quit))
+    (let ((buf (current-buffer)))
+      (if (not (one-window-p)) (delete-window))
+      (kill-buffer buf)
+      (org-agenda-reset-markers)
+      (org-columns-remove-overlays))
+    ;; Maybe restore the pre-agenda window configuration.
+    (and org-agenda-restore-windows-after-quit
+	 (not (eq org-agenda-window-setup 'other-frame))
+	 org-pre-agenda-window-conf
+	 (set-window-configuration org-pre-agenda-window-conf))))
+  
 (defun org-agenda-exit ()
   "Exit agenda by removing the window or the buffer.
 Also kill all Org-mode buffers which have been loaded by `org-agenda'.
@@ -3893,14 +3924,17 @@ So this is just a shortcut for `\\[org-agenda]', available in the agenda."
 When this is the global TODO list, a prefix argument will be interpreted."
   (interactive)
   (let* ((org-agenda-keep-modes t)
+	 (cols org-agenda-columns-active)
 	 (line (org-current-line))
 	 (window-line (- line (org-current-line (window-start))))
 	 (lprops (get 'org-agenda-redo-command 'org-lprops)))
+    (and cols (org-columns-quit))
     (message "Rebuilding agenda buffer...")
     (org-let lprops '(eval org-agenda-redo-command))
     (setq org-agenda-undo-list nil
 	  org-agenda-pending-undo-list nil)
     (message "Rebuilding agenda buffer...done")
+    (and cols (interactive-p) (org-agenda-columns))
     (goto-line line)
     (recenter window-line)))
 
@@ -4965,7 +4999,6 @@ This is a command that has to be installed in `calendar-mode-map'."
     (if (fboundp 'fit-window-to-buffer)
 	(fit-window-to-buffer (get-buffer-window "*Dates*")))))
 
-
 ;;; Appointment reminders
 
 (defvar appt-time-msg-list)

+ 101 - 9
lisp/org-colview.el

@@ -41,13 +41,17 @@
 
 (defvar org-columns-current-fmt nil
   "Local variable, holds the currently active column format.")
+(make-variable-buffer-local 'org-columns-current-fmt)
 (defvar org-columns-current-fmt-compiled nil
   "Local variable, holds the currently active column format.
 This is the compiled version of the format.")
+(make-variable-buffer-local 'org-columns-current-fmt-compiled)
 (defvar org-columns-current-widths nil
   "Loval variable, holds the currently widths of fields.")
+(make-variable-buffer-local 'org-columns-current-widths)
 (defvar org-columns-current-maxwidths nil
   "Loval variable, holds the currently active maximum column widths.")
+(make-variable-buffer-local 'org-columns-current-maxwidths)
 (defvar org-columns-begin-marker (make-marker)
   "Points to the position where last a column creation command was called.")
 (defvar org-columns-top-level-marker (make-marker)
@@ -132,8 +136,13 @@ This is the compiled version of the format.")
 		       (and (looking-at "\\(\\**\\)\\(\\* \\)")
 			    (org-get-level-face 2))))
 	 (color (list :foreground
-		      (face-attribute (or level-face 'default) :foreground)))
-	 props pom property ass width f string ov column val modval)
+		      (face-attribute
+		       (or level-face
+			   (and (eq major-mode 'org-agenda-mode)
+				(get-text-property (point-at-bol) 'face))
+			   'default) :foreground)))
+	 (face (list color 'org-column))
+	 pom property ass width f string ov column val modval)
     ;; Check if the entry is in another buffer.
     (unless props
       (if (eq major-mode 'org-agenda-mode)
@@ -162,9 +171,7 @@ This is the compiled version of the format.")
 	    string (format f (or modval val)))
       ;; Create the overlay
       (org-unmodified
-       (setq ov (org-columns-new-overlay
-		 beg (setq beg (1+ beg)) string
-		 (list color 'org-column)))
+       (setq ov (org-columns-new-overlay beg (setq beg (1+ beg)) string face))
        (org-overlay-put ov 'keymap org-columns-map)
        (org-overlay-put ov 'org-columns-key property)
        (org-overlay-put ov 'org-columns-value (cdr ass))
@@ -397,17 +404,22 @@ Where possible, use the standard interface for changing this line."
 (defun org-columns-edit-allowed ()
   "Edit the list of allowed values for the current property."
   (interactive)
-  (let* ((key (get-char-property (point) 'org-columns-key))
+  (let* ((pom (or (get-text-property (point-at-bol) 'org-marker)
+		  (get-text-property (point-at-bol) 'org-hd-marker)
+		  (point)))
+	 (key (get-char-property (point) 'org-columns-key))
 	 (key1 (concat key "_ALL"))
-	 (allowed (org-entry-get (point) key1 t))
+	 (allowed (org-entry-get pom key1 t))
 	 nval)
     ;; FIXME: Cover editing TODO, TAGS etc in-buffer settings.????
+    ;; FIXME: Write back to #+PROPERTY setting if that is needed.
     (setq nval (read-string "Allowed: " allowed))
     (org-entry-put
      (cond ((marker-position org-entry-property-inherited-from)
 	    org-entry-property-inherited-from)
 	   ((marker-position org-columns-top-level-marker)
-	    org-columns-top-level-marker))
+	    org-columns-top-level-marker)
+	   (t pom))
      key1 nval)))
 
 (defun org-columns-eval (form)
@@ -658,6 +670,7 @@ display, or in the #+COLUMNS line of the current buffer."
 (defvar org-agenda-view-columns-initially nil
   "When set, switch to columns view immediately after creating the agenda.")
 
+(defvar org-agenda-columns-show-summaries) ; defined in org-agenda.el
 (defun org-agenda-columns ()
   "Turn on column view in the agenda."
   (interactive)
@@ -685,6 +698,7 @@ display, or in the #+COLUMNS line of the current buffer."
     (setq fmt (or fmt org-columns-default-format))
     (org-set-local 'org-columns-current-fmt fmt)
     (org-columns-compile-format fmt)
+    (org-agenda-colview-compute org-columns-current-fmt-compiled)
     (save-excursion
       ;; Get and cache the properties
       (goto-char (point-min))
@@ -700,7 +714,84 @@ display, or in the #+COLUMNS line of the current buffer."
 	(mapc (lambda (x)
 		(goto-line (car x))
 		(org-columns-display-here (cdr x)))
-	      cache)))))
+	      cache)
+	(when org-agenda-columns-show-summaries
+	  (org-agenda-colview-summarize cache))))))
+
+(defun org-agenda-colview-summarize (cache)
+  "Summarize the summarizable columns in column view in the agenda.
+This will add overlays to the date lines, to show the summary for each day."
+  (let* ((fmt (mapcar (lambda (x)
+			(list (car x) (if (equal (car x) "CLOCKSUM")
+					  'add_times (nth 4 x))))
+		      org-columns-current-fmt-compiled))
+	 line c c1 stype props lsum entries prop v)
+    (when (delq nil (mapcar 'cadr fmt))
+      ;; OK, at least one summation column, it makes sense to try this
+      (goto-char (point-max))
+      (while (not (bobp))
+	(if (not (or (get-text-property (point) 'org-date-line)
+		     (eq (get-text-property (point) 'face)
+			 'org-agenda-structure)))
+	    (beginning-of-line 0)
+	  ;; OK, this is a date line
+	  (setq line (org-current-line))
+	  (setq entries nil c cache cache nil)
+	  (while (setq c1 (pop c))
+	    (if (> (car c1) line)
+		(push c1 entries)
+	      (push c1 cache)))
+	  ;; now ENTRIES are the ones we want to use, CACHE is the rest
+	  ;; Compute the summaries for the properties we want,
+	  ;; set nil properties for the rest.
+	  (when (setq entries (mapcar 'cdr entries))
+	    (setq props
+		  (mapcar
+		   (lambda (f)
+		     (setq prop (car f) stype (nth 1 f))
+		     (cond
+		      ((equal prop "ITEM")
+		       (cons prop (buffer-substring (point-at-bol)
+						    (point-at-eol))))
+		      ((not stype) (cons prop ""))
+		      (t
+		       ;; do the summary
+		       (setq lsum 0)
+		       (mapc (lambda (x)
+			       (setq v (cdr (assoc prop x)))
+			       (if v (setq lsum (+ lsum
+						   (org-column-string-to-number
+						    v stype)))))
+			     entries)
+		       (cons prop (org-columns-number-to-string lsum stype)))))
+		   fmt))
+	    (org-columns-display-here props)
+	    (org-set-local 'org-agenda-columns-active t))
+	  (beginning-of-line 0))))))
+
+(defvar org-agenda-columns-compute-summary-properties); defined in org-agenda.el
+(defun org-agenda-colview-compute (fmt)
+  "Compute the relevant columns in the contributing source buffers."
+  (when org-agenda-columns-compute-summary-properties
+    (let ((files org-agenda-contributing-files)
+	  (org-columns-begin-marker (make-marker))
+	  (org-columns-top-level-marker (make-marker))
+	  f fm a b)
+      (while (setq f (pop files))
+	(setq b (find-buffer-visiting f))
+	(with-current-buffer (or (buffer-base-buffer b) b)
+	  (save-excursion
+	    (save-restriction
+	      (goto-char (point-min))
+	      (org-columns-get-format-and-top-level)
+	      (while (setq fm (pop fmt))
+		(if (equal (car fm) "CLOCKSUM")
+		    (org-clock-sum)
+		  (when (and (nth 4 fm)
+			     (setq a (assoc (car fm)
+					    org-columns-current-fmt-compiled))
+			     (equal (nth 4 a) (nth 4 fm)))
+		    (org-columns-compute (car fm))))))))))))
 
 (defun org-columns-get-autowidth-alist (s cache)
   "Derive the maximum column widths from the format and the cache."
@@ -1056,3 +1147,4 @@ and tailing newline characters."
 (provide 'org-colview)
 
 ;;; org-colview.el ends here
+

+ 1 - 0
lisp/org.el

@@ -1324,6 +1324,7 @@ taken from the (otherwise obsolete) variable `org-todo-interpretation'."
 (make-variable-buffer-local 'org-todo-keywords-1)
 (defvar org-todo-keywords-for-agenda nil)
 (defvar org-done-keywords-for-agenda nil)
+(defvar org-agenda-contributing-files nil)
 (defvar org-not-done-keywords nil)
 (make-variable-buffer-local 'org-not-done-keywords)
 (defvar org-done-keywords nil)