From b0ba02839429e64bdd96502e92909a242f825357 Mon Sep 17 00:00:00 2001 From: Carsten Dominik Date: Tue, 15 Apr 2008 11:02:10 +0200 Subject: [PATCH] Implement Summaries for Column View in agenda. --- ChangeLog | 9 ++++ doc/org.texi | 58 ++++++++++++++++++++++- lisp/org-agenda.el | 59 ++++++++++++++++++------ lisp/org-colview.el | 110 ++++++++++++++++++++++++++++++++++++++++---- lisp/org.el | 1 + 5 files changed, 214 insertions(+), 23 deletions(-) diff --git a/ChangeLog b/ChangeLog index dcf3be109..9ae71a991 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,14 @@ 2008-04-15 Carsten Dominik + * 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 diff --git a/doc/org.texi b/doc/org.texi index 4fd1d8950..444d9d269 100644 --- a/doc/org.texi +++ b/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 diff --git a/lisp/org-agenda.el b/lisp/org-agenda.el index 2cbf93287..8c8c0921e 100644 --- a/lisp/org-agenda.el +++ b/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) diff --git a/lisp/org-colview.el b/lisp/org-colview.el index bc54ead1d..bdcc228da 100644 --- a/lisp/org-colview.el +++ b/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 + diff --git a/lisp/org.el b/lisp/org.el index 729eae8da..77895a862 100644 --- a/lisp/org.el +++ b/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)