* etc/publicsuffix.txt: Update from source.
[bpt/emacs.git] / lisp / org / org-colview.el
CommitLineData
20908596
CD
1;;; org-colview.el --- Column View in Org-mode
2
ba318903 3;; Copyright (C) 2004-2014 Free Software Foundation, Inc.
20908596
CD
4
5;; Author: Carsten Dominik <carsten at orgmode dot org>
6;; Keywords: outlines, hypermedia, calendar, wp
7;; Homepage: http://orgmode.org
20908596
CD
8;;
9;; This file is part of GNU Emacs.
10;;
b1fc2b50 11;; GNU Emacs is free software: you can redistribute it and/or modify
20908596 12;; it under the terms of the GNU General Public License as published by
b1fc2b50
GM
13;; the Free Software Foundation, either version 3 of the License, or
14;; (at your option) any later version.
20908596
CD
15
16;; GNU Emacs is distributed in the hope that it will be useful,
17;; but WITHOUT ANY WARRANTY; without even the implied warranty of
18;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19;; GNU General Public License for more details.
20
21;; You should have received a copy of the GNU General Public License
b1fc2b50 22;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
20908596
CD
23;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
24;;
25;;; Commentary:
26
33306645 27;; This file contains the column view for Org.
20908596
CD
28
29;;; Code:
30
31(eval-when-compile (require 'cl))
32(require 'org)
33
b349f79f 34(declare-function org-agenda-redo "org-agenda" ())
1bcdebed 35(declare-function org-agenda-do-context-action "org-agenda" ())
8223b1d2 36(declare-function org-clock-sum-today "org-clock" (&optional headline-filter))
b349f79f 37
86fbb8ca 38(when (featurep 'xemacs)
271672fa 39 (error "Do not load this file into XEmacs, use `org-colview-xemacs.el' from the contrib/ directory"))
86fbb8ca 40
20908596
CD
41;;; Column View
42
43(defvar org-columns-overlays nil
44 "Holds the list of current column overlays.")
45
46(defvar org-columns-current-fmt nil
47 "Local variable, holds the currently active column format.")
48(make-variable-buffer-local 'org-columns-current-fmt)
49(defvar org-columns-current-fmt-compiled nil
50 "Local variable, holds the currently active column format.
51This is the compiled version of the format.")
52(make-variable-buffer-local 'org-columns-current-fmt-compiled)
53(defvar org-columns-current-widths nil
54 "Loval variable, holds the currently widths of fields.")
55(make-variable-buffer-local 'org-columns-current-widths)
56(defvar org-columns-current-maxwidths nil
57 "Loval variable, holds the currently active maximum column widths.")
58(make-variable-buffer-local 'org-columns-current-maxwidths)
59(defvar org-columns-begin-marker (make-marker)
60 "Points to the position where last a column creation command was called.")
61(defvar org-columns-top-level-marker (make-marker)
62 "Points to the position where current columns region starts.")
63
64(defvar org-columns-map (make-sparse-keymap)
65 "The keymap valid in column display.")
66
67(defun org-columns-content ()
68 "Switch to contents view while in columns view."
69 (interactive)
70 (org-overview)
71 (org-content))
72
73(org-defkey org-columns-map "c" 'org-columns-content)
74(org-defkey org-columns-map "o" 'org-overview)
75(org-defkey org-columns-map "e" 'org-columns-edit-value)
76(org-defkey org-columns-map "\C-c\C-t" 'org-columns-todo)
77(org-defkey org-columns-map "\C-c\C-c" 'org-columns-set-tags-or-toggle)
78(org-defkey org-columns-map "\C-c\C-o" 'org-columns-open-link)
79(org-defkey org-columns-map "v" 'org-columns-show-value)
80(org-defkey org-columns-map "q" 'org-columns-quit)
81(org-defkey org-columns-map "r" 'org-columns-redo)
82(org-defkey org-columns-map "g" 'org-columns-redo)
83(org-defkey org-columns-map [left] 'backward-char)
84(org-defkey org-columns-map "\M-b" 'backward-char)
85(org-defkey org-columns-map "a" 'org-columns-edit-allowed)
86(org-defkey org-columns-map "s" 'org-columns-edit-attributes)
c8d0cf5c
CD
87(org-defkey org-columns-map "\M-f"
88 (lambda () (interactive) (goto-char (1+ (point)))))
89(org-defkey org-columns-map [right]
90 (lambda () (interactive) (goto-char (1+ (point)))))
91(org-defkey org-columns-map [down]
92 (lambda () (interactive)
93 (let ((col (current-column)))
94 (beginning-of-line 2)
95 (while (and (org-invisible-p2) (not (eobp)))
96 (beginning-of-line 2))
8bfe682a 97 (move-to-column col)
1bcdebed
CD
98 (if (eq major-mode 'org-agenda-mode)
99 (org-agenda-do-context-action)))))
c8d0cf5c
CD
100(org-defkey org-columns-map [up]
101 (lambda () (interactive)
102 (let ((col (current-column)))
103 (beginning-of-line 0)
104 (while (and (org-invisible-p2) (not (bobp)))
105 (beginning-of-line 0))
8bfe682a 106 (move-to-column col)
1bcdebed
CD
107 (if (eq major-mode 'org-agenda-mode)
108 (org-agenda-do-context-action)))))
20908596
CD
109(org-defkey org-columns-map [(shift right)] 'org-columns-next-allowed-value)
110(org-defkey org-columns-map "n" 'org-columns-next-allowed-value)
111(org-defkey org-columns-map [(shift left)] 'org-columns-previous-allowed-value)
112(org-defkey org-columns-map "p" 'org-columns-previous-allowed-value)
113(org-defkey org-columns-map "<" 'org-columns-narrow)
114(org-defkey org-columns-map ">" 'org-columns-widen)
115(org-defkey org-columns-map [(meta right)] 'org-columns-move-right)
116(org-defkey org-columns-map [(meta left)] 'org-columns-move-left)
117(org-defkey org-columns-map [(shift meta right)] 'org-columns-new)
118(org-defkey org-columns-map [(shift meta left)] 'org-columns-delete)
b349f79f
CD
119(dotimes (i 10)
120 (org-defkey org-columns-map (number-to-string i)
8bfe682a
CD
121 `(lambda () (interactive)
122 (org-columns-next-allowed-value nil ,i))))
20908596
CD
123
124(easy-menu-define org-columns-menu org-columns-map "Org Column Menu"
125 '("Column"
126 ["Edit property" org-columns-edit-value t]
127 ["Next allowed value" org-columns-next-allowed-value t]
128 ["Previous allowed value" org-columns-previous-allowed-value t]
129 ["Show full value" org-columns-show-value t]
130 ["Edit allowed values" org-columns-edit-allowed t]
131 "--"
132 ["Edit column attributes" org-columns-edit-attributes t]
133 ["Increase column width" org-columns-widen t]
134 ["Decrease column width" org-columns-narrow t]
135 "--"
136 ["Move column right" org-columns-move-right t]
137 ["Move column left" org-columns-move-left t]
138 ["Add column" org-columns-new t]
139 ["Delete column" org-columns-delete t]
140 "--"
141 ["CONTENTS" org-columns-content t]
142 ["OVERVIEW" org-overview t]
143 ["Refresh columns display" org-columns-redo t]
144 "--"
145 ["Open link" org-columns-open-link t]
146 "--"
147 ["Quit" org-columns-quit t]))
148
149(defun org-columns-new-overlay (beg end &optional string face)
150 "Create a new column overlay and add it to the list."
86fbb8ca
CD
151 (let ((ov (make-overlay beg end)))
152 (overlay-put ov 'face (or face 'secondary-selection))
8223b1d2 153 (remove-text-properties 0 (length string) '(face nil) string)
20908596
CD
154 (org-overlay-display ov string face)
155 (push ov org-columns-overlays)
156 ov))
157
ce4fdcb9 158(defun org-columns-display-here (&optional props dateline)
20908596
CD
159 "Overlay the current line with column display."
160 (interactive)
161 (let* ((fmt org-columns-current-fmt-compiled)
162 (beg (point-at-bol))
163 (level-face (save-excursion
164 (beginning-of-line 1)
165 (and (looking-at "\\(\\**\\)\\(\\* \\)")
166 (org-get-level-face 2))))
167 (ref-face (or level-face
168 (and (eq major-mode 'org-agenda-mode)
169 (get-text-property (point-at-bol) 'face))
170 'default))
b349f79f 171 (color (list :foreground (face-attribute ref-face :foreground)))
271672fa
BG
172 (font (list :height (face-attribute 'default :height)
173 :family (face-attribute 'default :family)))
174 (face (list color font 'org-column ref-face))
175 (face1 (list color font 'org-agenda-column-dateline ref-face))
b349f79f 176 (cphr (get-text-property (point-at-bol) 'org-complex-heading-regexp))
30cb51f1 177 pom property ass width f fc string fm ov column val modval s2 title calc)
20908596
CD
178 ;; Check if the entry is in another buffer.
179 (unless props
180 (if (eq major-mode 'org-agenda-mode)
8d642074
CD
181 (setq pom (or (org-get-at-bol 'org-hd-marker)
182 (org-get-at-bol 'org-marker))
20908596
CD
183 props (if pom (org-entry-properties pom) nil))
184 (setq props (org-entry-properties nil))))
185 ;; Walk the format
186 (while (setq column (pop fmt))
187 (setq property (car column)
621f83e4 188 title (nth 1 column)
20908596
CD
189 ass (if (equal property "ITEM")
190 (cons "ITEM"
3ab2c837
BG
191 ;; When in a buffer, get the whole line,
192 ;; we'll clean it later…
8223b1d2 193 (if (derived-mode-p 'org-mode)
3ab2c837 194 (save-match-data
8223b1d2
BG
195 (org-remove-tabs
196 (buffer-substring-no-properties
197 (point-at-bol) (point-at-eol))))
3ab2c837 198 ;; In agenda, just get the `txt' property
8223b1d2
BG
199 (or (org-get-at-bol 'txt)
200 (buffer-substring-no-properties
201 (point) (progn (end-of-line) (point))))))
20908596
CD
202 (assoc property props))
203 width (or (cdr (assoc property org-columns-current-maxwidths))
204 (nth 2 column)
205 (length property))
206 f (format "%%-%d.%ds | " width width)
30cb51f1
BG
207 fm (nth 4 column)
208 fc (nth 5 column)
8bfe682a 209 calc (nth 7 column)
20908596 210 val (or (cdr ass) "")
8bfe682a
CD
211 modval (cond ((and org-columns-modify-value-for-display-function
212 (functionp
213 org-columns-modify-value-for-display-function))
214 (funcall org-columns-modify-value-for-display-function
215 title val))
216 ((equal property "ITEM")
e66ba1df
BG
217 (org-columns-cleanup-item
218 val org-columns-current-fmt-compiled
219 (or org-complex-heading-regexp cphr)))
30cb51f1
BG
220 (fc (org-columns-number-to-string
221 (org-columns-string-to-number val fm) fm fc))
8bfe682a
CD
222 ((and calc (functionp calc)
223 (not (string= val ""))
224 (not (get-text-property 0 'org-computed val)))
225 (org-columns-number-to-string
226 (funcall calc (org-columns-string-to-number
30cb51f1 227 val fm)) fm))))
b349f79f
CD
228 (setq s2 (org-columns-add-ellipses (or modval val) width))
229 (setq string (format f s2))
20908596 230 ;; Create the overlay
271672fa 231 (org-with-silent-modifications
20908596 232 (setq ov (org-columns-new-overlay
ce4fdcb9 233 beg (setq beg (1+ beg)) string (if dateline face1 face)))
86fbb8ca
CD
234 (overlay-put ov 'keymap org-columns-map)
235 (overlay-put ov 'org-columns-key property)
236 (overlay-put ov 'org-columns-value (cdr ass))
237 (overlay-put ov 'org-columns-value-modified modval)
238 (overlay-put ov 'org-columns-pom pom)
afe98dfa
CD
239 (overlay-put ov 'org-columns-format f)
240 (overlay-put ov 'line-prefix "")
241 (overlay-put ov 'wrap-prefix ""))
20908596
CD
242 (if (or (not (char-after beg))
243 (equal (char-after beg) ?\n))
244 (let ((inhibit-read-only t))
245 (save-excursion
246 (goto-char beg)
247 (org-unmodified (insert " ")))))) ;; FIXME: add props and remove later?
8223b1d2
BG
248 ;; Make the rest of the line disappear.
249 (org-unmodified
250 (setq ov (org-columns-new-overlay beg (point-at-eol)))
251 (overlay-put ov 'invisible t)
252 (overlay-put ov 'keymap org-columns-map)
253 (overlay-put ov 'intangible t)
254 (overlay-put ov 'line-prefix "")
255 (overlay-put ov 'wrap-prefix "")
256 (push ov org-columns-overlays)
257 (setq ov (make-overlay (1- (point-at-eol)) (1+ (point-at-eol))))
258 (overlay-put ov 'keymap org-columns-map)
259 (push ov org-columns-overlays)
260 (let ((inhibit-read-only t))
261 (put-text-property (max (point-min) (1- (point-at-bol)))
20908596
CD
262 (min (point-max) (1+ (point-at-eol)))
263 'read-only "Type `e' to edit property")))))
264
b349f79f
CD
265(defun org-columns-add-ellipses (string width)
266 "Truncate STRING with WIDTH characters, with ellipses."
ff4be292 267 (cond
b349f79f
CD
268 ((<= (length string) width) string)
269 ((<= width (length org-columns-ellipses))
270 (substring org-columns-ellipses 0 width))
271 (t (concat (substring string 0 (- width (length org-columns-ellipses)))
272 org-columns-ellipses))))
273
20908596 274(defvar org-columns-full-header-line-format nil
33306645 275 "The full header line format, will be shifted by horizontal scrolling." )
20908596
CD
276(defvar org-previous-header-line-format nil
277 "The header line format before column view was turned on.")
278(defvar org-columns-inhibit-recalculation nil
279 "Inhibit recomputing of columns on column view startup.")
280(defvar org-columns-flyspell-was-active nil
281 "Remember the state of `flyspell-mode' before column view.
282Flyspell-mode can cause problems in columns view, so it is turned off
283for the duration of the command.")
284
285(defvar header-line-format)
286(defvar org-columns-previous-hscroll 0)
8bfe682a 287
20908596
CD
288(defun org-columns-display-here-title ()
289 "Overlay the newline before the current line with the table title."
290 (interactive)
291 (let ((fmt org-columns-current-fmt-compiled)
292 string (title "")
293 property width f column str widths)
294 (while (setq column (pop fmt))
295 (setq property (car column)
296 str (or (nth 1 column) property)
297 width (or (cdr (assoc property org-columns-current-maxwidths))
298 (nth 2 column)
299 (length str))
300 widths (push width widths)
301 f (format "%%-%d.%ds | " width width)
302 string (format f str)
303 title (concat title string)))
304 (setq title (concat
305 (org-add-props " " nil 'display '(space :align-to 0))
306 ;;(org-add-props title nil 'face '(:weight bold :underline t :inherit default))))
307 (org-add-props title nil 'face 'org-column-title)))
308 (org-set-local 'org-previous-header-line-format header-line-format)
309 (org-set-local 'org-columns-current-widths (nreverse widths))
310 (setq org-columns-full-header-line-format title)
311 (setq org-columns-previous-hscroll -1)
8223b1d2 312 ; (org-columns-hscoll-title)
20908596
CD
313 (org-add-hook 'post-command-hook 'org-columns-hscoll-title nil 'local)))
314
315(defun org-columns-hscoll-title ()
86fbb8ca 316 "Set the `header-line-format' so that it scrolls along with the table."
20908596
CD
317 (sit-for .0001) ; need to force a redisplay to update window-hscroll
318 (when (not (= (window-hscroll) org-columns-previous-hscroll))
319 (setq header-line-format
320 (concat (substring org-columns-full-header-line-format 0 1)
321 (substring org-columns-full-header-line-format
322 (1+ (window-hscroll))))
323 org-columns-previous-hscroll (window-hscroll))
324 (force-mode-line-update)))
325
c8d0cf5c
CD
326(defvar org-colview-initial-truncate-line-value nil
327 "Remember the value of `truncate-lines' across colview.")
328
73d3db82 329;;;###autoload
20908596
CD
330(defun org-columns-remove-overlays ()
331 "Remove all currently active column overlays."
332 (interactive)
333 (when (marker-buffer org-columns-begin-marker)
334 (with-current-buffer (marker-buffer org-columns-begin-marker)
335 (when (local-variable-p 'org-previous-header-line-format)
336 (setq header-line-format org-previous-header-line-format)
337 (kill-local-variable 'org-previous-header-line-format)
338 (remove-hook 'post-command-hook 'org-columns-hscoll-title 'local))
339 (move-marker org-columns-begin-marker nil)
340 (move-marker org-columns-top-level-marker nil)
271672fa 341 (org-with-silent-modifications
86fbb8ca 342 (mapc 'delete-overlay org-columns-overlays)
20908596
CD
343 (setq org-columns-overlays nil)
344 (let ((inhibit-read-only t))
345 (remove-text-properties (point-min) (point-max) '(read-only t))))
346 (when org-columns-flyspell-was-active
c8d0cf5c
CD
347 (flyspell-mode 1))
348 (when (local-variable-p 'org-colview-initial-truncate-line-value)
349 (setq truncate-lines org-colview-initial-truncate-line-value)))))
20908596 350
e66ba1df
BG
351(defun org-columns-cleanup-item (item fmt cphr)
352 "Remove from ITEM what is a column in the format FMT.
353CPHR is the complex heading regexp to use for parsing ITEM."
354 (let (fixitem)
355 (if (not cphr)
356 item
357 (unless (string-match "^\*+ " item)
358 (setq item (concat "* " item) fixitem t))
359 (if (string-match cphr item)
360 (setq item
361 (concat
362 (org-add-props (match-string 1 item) nil
363 'org-whitespace (* 2 (1- (org-reduced-level (- (match-end 1) (match-beginning 1))))))
364 (and (match-end 2) (not (assoc "TODO" fmt)) (concat " " (match-string 2 item)))
365 (and (match-end 3) (not (assoc "PRIORITY" fmt)) (concat " " (match-string 3 item)))
153ae947 366 " " (save-match-data (org-columns-compact-links (or (match-string 4 item) "")))
e66ba1df
BG
367 (and (match-end 5) (not (assoc "TAGS" fmt)) (concat " " (match-string 5 item)))))
368 (add-text-properties
369 0 (1+ (match-end 1))
370 (list 'org-whitespace (* 2 (1- (org-reduced-level (- (match-end 1) (match-beginning 1))))))
371 item))
372 (if fixitem (replace-regexp-in-string "^\*+ " "" item) item))))
b349f79f
CD
373
374(defun org-columns-compact-links (s)
375 "Replace [[link][desc]] with [desc] or [link]."
376 (while (string-match org-bracket-link-regexp s)
377 (setq s (replace-match
378 (concat "[" (match-string (if (match-end 3) 3 1) s) "]")
379 t t s)))
380 s)
381
20908596
CD
382(defun org-columns-show-value ()
383 "Show the full value of the property."
384 (interactive)
385 (let ((value (get-char-property (point) 'org-columns-value)))
386 (message "Value is: %s" (or value ""))))
387
388(defvar org-agenda-columns-active) ;; defined in org-agenda.el
8bfe682a 389
20908596
CD
390(defun org-columns-quit ()
391 "Remove the column overlays and in this way exit column editing."
392 (interactive)
271672fa 393 (org-with-silent-modifications
20908596
CD
394 (org-columns-remove-overlays)
395 (let ((inhibit-read-only t))
396 (remove-text-properties (point-min) (point-max) '(read-only t))))
397 (when (eq major-mode 'org-agenda-mode)
398 (setq org-agenda-columns-active nil)
399 (message
400 "Modification not yet reflected in Agenda buffer, use `r' to refresh")))
401
402(defun org-columns-check-computed ()
403 "Check if this column value is computed.
404If yes, throw an error indicating that changing it does not make sense."
405 (let ((val (get-char-property (point) 'org-columns-value)))
406 (when (and (stringp val)
407 (get-char-property 0 'org-computed val))
408 (error "This value is computed from the entry's children"))))
409
410(defun org-columns-todo (&optional arg)
411 "Change the TODO state during column view."
412 (interactive "P")
413 (org-columns-edit-value "TODO"))
414
415(defun org-columns-set-tags-or-toggle (&optional arg)
416 "Toggle checkbox at point, or set tags for current headline."
417 (interactive "P")
418 (if (string-match "\\`\\[[ xX-]\\]\\'"
419 (get-char-property (point) 'org-columns-value))
420 (org-columns-next-allowed-value)
421 (org-columns-edit-value "TAGS")))
422
666ffc7e
SM
423(defvar org-agenda-overriding-columns-format nil
424 "When set, overrides any other format definition for the agenda.
425Don't set this, this is meant for dynamic scoping.")
426
20908596
CD
427(defun org-columns-edit-value (&optional key)
428 "Edit the value of the property at point in column view.
429Where possible, use the standard interface for changing this line."
430 (interactive)
431 (org-columns-check-computed)
9148fdd0 432 (let* ((col (current-column))
20908596
CD
433 (key (or key (get-char-property (point) 'org-columns-key)))
434 (value (get-char-property (point) 'org-columns-value))
435 (bol (point-at-bol)) (eol (point-at-eol))
436 (pom (or (get-text-property bol 'org-hd-marker)
437 (point))) ; keep despite of compiler waring
438 (line-overlays
439 (delq nil (mapcar (lambda (x)
440 (and (eq (overlay-buffer x) (current-buffer))
441 (>= (overlay-start x) bol)
442 (<= (overlay-start x) eol)
443 x))
444 org-columns-overlays)))
8bfe682a 445 (org-columns-time (time-to-number-of-days (current-time)))
20908596
CD
446 nval eval allowed)
447 (cond
448 ((equal key "CLOCKSUM")
449 (error "This special column cannot be edited"))
450 ((equal key "ITEM")
451 (setq eval '(org-with-point-at pom
452 (org-edit-headline))))
453 ((equal key "TODO")
c8d0cf5c 454 (setq eval '(org-with-point-at
8223b1d2
BG
455 pom
456 (call-interactively 'org-todo))))
20908596
CD
457 ((equal key "PRIORITY")
458 (setq eval '(org-with-point-at pom
459 (call-interactively 'org-priority))))
460 ((equal key "TAGS")
461 (setq eval '(org-with-point-at pom
462 (let ((org-fast-tag-selection-single-key
463 (if (eq org-fast-tag-selection-single-key 'expert)
464 t org-fast-tag-selection-single-key)))
465 (call-interactively 'org-set-tags)))))
466 ((equal key "DEADLINE")
467 (setq eval '(org-with-point-at pom
468 (call-interactively 'org-deadline))))
469 ((equal key "SCHEDULED")
470 (setq eval '(org-with-point-at pom
471 (call-interactively 'org-schedule))))
ed21c5c8
CD
472 ((equal key "BEAMER_env")
473 (setq eval '(org-with-point-at pom
afe98dfa 474 (call-interactively 'org-beamer-select-environment))))
20908596
CD
475 (t
476 (setq allowed (org-property-get-allowed-values pom key 'table))
477 (if allowed
ed21c5c8
CD
478 (setq nval (org-icompleting-read
479 "Value: " allowed nil
480 (not (get-text-property 0 'org-unrestricted
481 (caar allowed)))))
20908596
CD
482 (setq nval (read-string "Edit: " value)))
483 (setq nval (org-trim nval))
484 (when (not (equal nval value))
485 (setq eval '(org-entry-put pom key nval)))))
486 (when eval
487
488 (cond
489 ((equal major-mode 'org-agenda-mode)
b349f79f 490 (org-columns-eval eval)
20908596 491 ;; The following let preserves the current format, and makes sure
ee7683eb 492 ;; that in only a single file things need to be updated.
20908596
CD
493 (let* ((org-agenda-overriding-columns-format org-columns-current-fmt)
494 (buffer (marker-buffer pom))
495 (org-agenda-contributing-files
496 (list (with-current-buffer buffer
497 (buffer-file-name (buffer-base-buffer))))))
498 (org-agenda-columns)))
499 (t
500 (let ((inhibit-read-only t))
271672fa 501 (org-with-silent-modifications
20908596
CD
502 (remove-text-properties
503 (max (point-min) (1- bol)) eol '(read-only t)))
504 (unwind-protect
505 (progn
506 (setq org-columns-overlays
507 (org-delete-all line-overlays org-columns-overlays))
86fbb8ca 508 (mapc 'delete-overlay line-overlays)
20908596
CD
509 (org-columns-eval eval))
510 (org-columns-display-here)))
511 (org-move-to-column col)
8223b1d2 512 (if (and (derived-mode-p 'org-mode)
20908596
CD
513 (nth 3 (assoc key org-columns-current-fmt-compiled)))
514 (org-columns-update key)))))))
515
516(defun org-edit-headline () ; FIXME: this is not columns specific. Make interactive????? Use from agenda????
517 "Edit the current headline, the part without TODO keyword, TAGS."
518 (org-back-to-heading)
519 (when (looking-at org-todo-line-regexp)
b349f79f
CD
520 (let ((pos (point))
521 (pre (buffer-substring (match-beginning 0) (match-beginning 3)))
20908596
CD
522 (txt (match-string 3))
523 (post "")
524 txt2)
afe98dfa 525 (if (string-match (org-re "[ \t]+:[[:alnum:]:_@#%]+:[ \t]*$") txt)
20908596
CD
526 (setq post (match-string 0 txt)
527 txt (substring txt 0 (match-beginning 0))))
528 (setq txt2 (read-string "Edit: " txt))
529 (when (not (equal txt txt2))
b349f79f 530 (goto-char pos)
20908596
CD
531 (insert pre txt2 post)
532 (delete-region (point) (point-at-eol))
533 (org-set-tags nil t)))))
534
535(defun org-columns-edit-allowed ()
536 "Edit the list of allowed values for the current property."
537 (interactive)
8d642074
CD
538 (let* ((pom (or (org-get-at-bol 'org-marker)
539 (org-get-at-bol 'org-hd-marker)
20908596
CD
540 (point)))
541 (key (get-char-property (point) 'org-columns-key))
542 (key1 (concat key "_ALL"))
543 (allowed (org-entry-get pom key1 t))
544 nval)
545 ;; FIXME: Cover editing TODO, TAGS etc in-buffer settings.????
546 ;; FIXME: Write back to #+PROPERTY setting if that is needed.
547 (setq nval (read-string "Allowed: " allowed))
548 (org-entry-put
549 (cond ((marker-position org-entry-property-inherited-from)
550 org-entry-property-inherited-from)
551 ((marker-position org-columns-top-level-marker)
552 org-columns-top-level-marker)
553 (t pom))
554 key1 nval)))
555
556(defun org-columns-eval (form)
557 (let (hidep)
558 (save-excursion
559 (beginning-of-line 1)
560 ;; `next-line' is needed here, because it skips invisible line.
561 (condition-case nil (org-no-warnings (next-line 1)) (error nil))
e66ba1df 562 (setq hidep (org-at-heading-p 1)))
20908596
CD
563 (eval form)
564 (and hidep (hide-entry))))
565
566(defun org-columns-previous-allowed-value ()
567 "Switch to the previous allowed value for this column."
568 (interactive)
569 (org-columns-next-allowed-value t))
570
b349f79f
CD
571(defun org-columns-next-allowed-value (&optional previous nth)
572 "Switch to the next allowed value for this column.
573When PREVIOUS is set, go to the previous value. When NTH is
574an integer, select that value."
20908596
CD
575 (interactive)
576 (org-columns-check-computed)
577 (let* ((col (current-column))
578 (key (get-char-property (point) 'org-columns-key))
579 (value (get-char-property (point) 'org-columns-value))
580 (bol (point-at-bol)) (eol (point-at-eol))
581 (pom (or (get-text-property bol 'org-hd-marker)
582 (point))) ; keep despite of compiler waring
583 (line-overlays
584 (delq nil (mapcar (lambda (x)
585 (and (eq (overlay-buffer x) (current-buffer))
586 (>= (overlay-start x) bol)
587 (<= (overlay-start x) eol)
588 x))
589 org-columns-overlays)))
590 (allowed (or (org-property-get-allowed-values pom key)
591 (and (memq
592 (nth 4 (assoc key org-columns-current-fmt-compiled))
593 '(checkbox checkbox-n-of-m checkbox-percent))
621f83e4
CD
594 '("[ ]" "[X]"))
595 (org-colview-construct-allowed-dates value)))
20908596 596 nval)
b349f79f
CD
597 (when (integerp nth)
598 (setq nth (1- nth))
599 (if (= nth -1) (setq nth 9)))
20908596
CD
600 (when (equal key "ITEM")
601 (error "Cannot edit item headline from here"))
271672fa 602 (unless (or allowed (member key '("SCHEDULED" "DEADLINE" "CLOCKSUM")))
20908596 603 (error "Allowed values for this property have not been defined"))
271672fa 604 (if (member key '("SCHEDULED" "DEADLINE" "CLOCKSUM"))
20908596
CD
605 (setq nval (if previous 'earlier 'later))
606 (if previous (setq allowed (reverse allowed)))
b349f79f
CD
607 (cond
608 (nth
609 (setq nval (nth nth allowed))
610 (if (not nval)
611 (error "There are only %d allowed values for property `%s'"
612 (length allowed) key)))
613 ((member value allowed)
614 (setq nval (or (car (cdr (member value allowed)))
615 (car allowed)))
616 (if (equal nval value)
617 (error "Only one allowed value for this property")))
618 (t (setq nval (car allowed)))))
20908596
CD
619 (cond
620 ((equal major-mode 'org-agenda-mode)
621 (org-columns-eval '(org-entry-put pom key nval))
622 ;; The following let preserves the current format, and makes sure
ee7683eb 623 ;; that in only a single file things need to be updated.
20908596
CD
624 (let* ((org-agenda-overriding-columns-format org-columns-current-fmt)
625 (buffer (marker-buffer pom))
626 (org-agenda-contributing-files
627 (list (with-current-buffer buffer
628 (buffer-file-name (buffer-base-buffer))))))
629 (org-agenda-columns)))
630 (t
631 (let ((inhibit-read-only t))
632 (remove-text-properties (1- bol) eol '(read-only t))
633 (unwind-protect
634 (progn
635 (setq org-columns-overlays
636 (org-delete-all line-overlays org-columns-overlays))
86fbb8ca 637 (mapc 'delete-overlay line-overlays)
20908596
CD
638 (org-columns-eval '(org-entry-put pom key nval)))
639 (org-columns-display-here)))
640 (org-move-to-column col)
641 (and (nth 3 (assoc key org-columns-current-fmt-compiled))
642 (org-columns-update key))))))
643
621f83e4
CD
644(defun org-colview-construct-allowed-dates (s)
645 "Construct a list of three dates around the date in S.
646This respects the format of the time stamp in S, active or non-active,
647and also including time or not. S must be just a time stamp, no text
648around it."
0bd48b37 649 (when (and s (string-match (concat "^" org-ts-regexp3 "$") s))
621f83e4
CD
650 (let* ((time (org-parse-time-string s 'nodefaults))
651 (active (equal (string-to-char s) ?<))
652 (fmt (funcall (if (nth 1 time) 'cdr 'car) org-time-stamp-formats))
653 time-before time-after)
654 (unless active (setq fmt (concat "[" (substring fmt 1 -1) "]")))
655 (setf (car time) (or (car time) 0))
656 (setf (nth 1 time) (or (nth 1 time) 0))
657 (setf (nth 2 time) (or (nth 2 time) 0))
658 (setq time-before (copy-sequence time))
659 (setq time-after (copy-sequence time))
660 (setf (nth 3 time-before) (1- (nth 3 time)))
661 (setf (nth 3 time-after) (1+ (nth 3 time)))
662 (mapcar (lambda (x) (format-time-string fmt (apply 'encode-time x)))
663 (list time-before time time-after)))))
664
20908596
CD
665(defun org-verify-version (task)
666 (cond
667 ((eq task 'columns)
668 (if (or (featurep 'xemacs)
669 (< emacs-major-version 22))
670 (error "Emacs 22 is required for the columns feature")))))
671
672(defun org-columns-open-link (&optional arg)
673 (interactive "P")
674 (let ((value (get-char-property (point) 'org-columns-value)))
675 (org-open-link-from-string value arg)))
676
73d3db82 677;;;###autoload
20908596 678(defun org-columns-get-format-and-top-level ()
8223b1d2
BG
679 (let ((fmt (org-columns-get-format)))
680 (org-columns-goto-top-level)
681 fmt))
682
683(defun org-columns-get-format (&optional fmt-string)
684 (interactive)
685 (let (fmt-as-property fmt)
20908596 686 (when (condition-case nil (org-back-to-heading) (error nil))
8223b1d2
BG
687 (setq fmt-as-property (org-entry-get nil "COLUMNS" t)))
688 (setq fmt (or fmt-string fmt-as-property org-columns-default-format))
20908596
CD
689 (org-set-local 'org-columns-current-fmt fmt)
690 (org-columns-compile-format fmt)
20908596
CD
691 fmt))
692
8223b1d2
BG
693(defun org-columns-goto-top-level ()
694 (when (condition-case nil (org-back-to-heading) (error nil))
695 (org-entry-get nil "COLUMNS" t))
696 (if (marker-position org-entry-property-inherited-from)
697 (move-marker org-columns-top-level-marker org-entry-property-inherited-from)
698 (move-marker org-columns-top-level-marker (point))))
699
bdebdb64 700;;;###autoload
8223b1d2
BG
701(defun org-columns (&optional columns-fmt-string)
702 "Turn on column view on an org-mode file.
703When COLUMNS-FMT-STRING is non-nil, use it as the column format."
20908596
CD
704 (interactive)
705 (org-verify-version 'columns)
706 (org-columns-remove-overlays)
707 (move-marker org-columns-begin-marker (point))
8bfe682a
CD
708 (let ((org-columns-time (time-to-number-of-days (current-time)))
709 beg end fmt cache maxwidths)
8223b1d2
BG
710 (org-columns-goto-top-level)
711 (setq fmt (org-columns-get-format columns-fmt-string))
20908596
CD
712 (save-excursion
713 (goto-char org-columns-top-level-marker)
714 (setq beg (point))
715 (unless org-columns-inhibit-recalculation
716 (org-columns-compute-all))
717 (setq end (or (condition-case nil (org-end-of-subtree t t) (error nil))
718 (point-max)))
719 ;; Get and cache the properties
720 (goto-char beg)
721 (when (assoc "CLOCKSUM" org-columns-current-fmt-compiled)
722 (save-excursion
723 (save-restriction
724 (narrow-to-region beg end)
725 (org-clock-sum))))
8223b1d2
BG
726 (when (assoc "CLOCKSUM_T" org-columns-current-fmt-compiled)
727 (save-excursion
728 (save-restriction
729 (narrow-to-region beg end)
730 (org-clock-sum-today))))
3ab2c837 731 (while (re-search-forward org-outline-regexp-bol end t)
8bfe682a 732 (if (and org-columns-skip-archived-trees
c8d0cf5c
CD
733 (looking-at (concat ".*:" org-archive-tag ":")))
734 (org-end-of-subtree t)
735 (push (cons (org-current-line) (org-entry-properties)) cache)))
20908596
CD
736 (when cache
737 (setq maxwidths (org-columns-get-autowidth-alist fmt cache))
738 (org-set-local 'org-columns-current-maxwidths maxwidths)
739 (org-columns-display-here-title)
740 (when (org-set-local 'org-columns-flyspell-was-active
741 (org-bound-and-true-p flyspell-mode))
742 (flyspell-mode 0))
c8d0cf5c
CD
743 (unless (local-variable-p 'org-colview-initial-truncate-line-value)
744 (org-set-local 'org-colview-initial-truncate-line-value
745 truncate-lines))
746 (setq truncate-lines t)
20908596 747 (mapc (lambda (x)
54a0dee5 748 (org-goto-line (car x))
20908596
CD
749 (org-columns-display-here (cdr x)))
750 cache)))))
751
8bfe682a
CD
752(eval-when-compile (defvar org-columns-time))
753
c8d0cf5c 754(defvar org-columns-compile-map
8bfe682a
CD
755 '(("none" none +)
756 (":" add_times +)
757 ("+" add_numbers +)
758 ("$" currency +)
759 ("X" checkbox +)
760 ("X/" checkbox-n-of-m +)
761 ("X%" checkbox-percent +)
762 ("max" max_numbers max)
763 ("min" min_numbers min)
764 ("mean" mean_numbers
765 (lambda (&rest x) (/ (apply '+ x) (float (length x)))))
766 (":max" max_times max)
767 (":min" min_times min)
768 (":mean" mean_times
769 (lambda (&rest x) (/ (apply '+ x) (float (length x)))))
770 ("@min" min_age min (lambda (x) (- org-columns-time x)))
771 ("@max" max_age max (lambda (x) (- org-columns-time x)))
772 ("@mean" mean_age
773 (lambda (&rest x) (/ (apply '+ x) (float (length x))))
afe98dfa
CD
774 (lambda (x) (- org-columns-time x)))
775 ("est+" estimate org-estimate-combine))
8bfe682a 776 "Operator <-> format,function,calc map.
c8d0cf5c 777Used to compile/uncompile columns format and completing read in
86fbb8ca 778interactive function `org-columns-new'.
8bfe682a
CD
779
780operator string used in #+COLUMNS definition describing the
781 summary type
782format symbol describing summary type selected interactively in
86fbb8ca
CD
783 `org-columns-new' and internally in
784 `org-columns-number-to-string' and
785 `org-columns-string-to-number'
8bfe682a
CD
786function called with a list of values as argument to calculate
787 the summary value
86fbb8ca 788calc function called on every element before summarizing. This is
8bfe682a 789 optional and should only be specified if needed")
c8d0cf5c
CD
790
791(defun org-columns-new (&optional prop title width op fmt fun &rest rest)
20908596
CD
792 "Insert a new column, to the left of the current column."
793 (interactive)
794 (let ((editp (and prop (assoc prop org-columns-current-fmt-compiled)))
795 cell)
54a0dee5 796 (setq prop (org-icompleting-read
20908596
CD
797 "Property: " (mapcar 'list (org-buffer-property-keys t nil t))
798 nil nil prop))
799 (setq title (read-string (concat "Column title [" prop "]: ") (or title prop)))
800 (setq width (read-string "Column width: " (if width (number-to-string width))))
801 (if (string-match "\\S-" width)
802 (setq width (string-to-number width))
803 (setq width nil))
54a0dee5 804 (setq fmt (org-icompleting-read
c8d0cf5c
CD
805 "Summary [none]: "
806 (mapcar (lambda (x) (list (symbol-name (cadr x))))
807 org-columns-compile-map)
808 nil t))
809 (setq fmt (intern fmt)
8bfe682a 810 fun (cdr (assoc fmt (mapcar 'cdr org-columns-compile-map))))
20908596
CD
811 (if (eq fmt 'none) (setq fmt nil))
812 (if editp
813 (progn
814 (setcar editp prop)
c8d0cf5c 815 (setcdr editp (list title width nil fmt nil fun)))
20908596
CD
816 (setq cell (nthcdr (1- (current-column))
817 org-columns-current-fmt-compiled))
8bfe682a
CD
818 (setcdr cell (cons (list prop title width nil fmt nil
819 (car fun) (cadr fun))
20908596
CD
820 (cdr cell))))
821 (org-columns-store-format)
822 (org-columns-redo)))
823
824(defun org-columns-delete ()
825 "Delete the column at point from columns view."
826 (interactive)
827 (let* ((n (current-column))
828 (title (nth 1 (nth n org-columns-current-fmt-compiled))))
829 (when (y-or-n-p
830 (format "Are you sure you want to remove column \"%s\"? " title))
831 (setq org-columns-current-fmt-compiled
832 (delq (nth n org-columns-current-fmt-compiled)
833 org-columns-current-fmt-compiled))
834 (org-columns-store-format)
835 (org-columns-redo)
836 (if (>= (current-column) (length org-columns-current-fmt-compiled))
837 (backward-char 1)))))
838
839(defun org-columns-edit-attributes ()
840 "Edit the attributes of the current column."
841 (interactive)
842 (let* ((n (current-column))
843 (info (nth n org-columns-current-fmt-compiled)))
844 (apply 'org-columns-new info)))
845
846(defun org-columns-widen (arg)
847 "Make the column wider by ARG characters."
848 (interactive "p")
849 (let* ((n (current-column))
850 (entry (nth n org-columns-current-fmt-compiled))
851 (width (or (nth 2 entry)
852 (cdr (assoc (car entry) org-columns-current-maxwidths)))))
853 (setq width (max 1 (+ width arg)))
854 (setcar (nthcdr 2 entry) width)
855 (org-columns-store-format)
856 (org-columns-redo)))
857
858(defun org-columns-narrow (arg)
33306645 859 "Make the column narrower by ARG characters."
20908596
CD
860 (interactive "p")
861 (org-columns-widen (- arg)))
862
863(defun org-columns-move-right ()
864 "Swap this column with the one to the right."
865 (interactive)
866 (let* ((n (current-column))
867 (cell (nthcdr n org-columns-current-fmt-compiled))
868 e)
869 (when (>= n (1- (length org-columns-current-fmt-compiled)))
870 (error "Cannot shift this column further to the right"))
871 (setq e (car cell))
872 (setcar cell (car (cdr cell)))
873 (setcdr cell (cons e (cdr (cdr cell))))
874 (org-columns-store-format)
875 (org-columns-redo)
876 (forward-char 1)))
877
878(defun org-columns-move-left ()
879 "Swap this column with the one to the left."
880 (interactive)
881 (let* ((n (current-column)))
882 (when (= n 0)
883 (error "Cannot shift this column further to the left"))
884 (backward-char 1)
885 (org-columns-move-right)
886 (backward-char 1)))
887
888(defun org-columns-store-format ()
889 "Store the text version of the current columns format in appropriate place.
890This is either in the COLUMNS property of the node starting the current column
891display, or in the #+COLUMNS line of the current buffer."
892 (let (fmt (cnt 0))
893 (setq fmt (org-columns-uncompile-format org-columns-current-fmt-compiled))
894 (org-set-local 'org-columns-current-fmt fmt)
895 (if (marker-position org-columns-top-level-marker)
896 (save-excursion
897 (goto-char org-columns-top-level-marker)
898 (if (and (org-at-heading-p)
899 (org-entry-get nil "COLUMNS"))
900 (org-entry-put nil "COLUMNS" fmt)
901 (goto-char (point-min))
902 ;; Overwrite all #+COLUMNS lines....
903 (while (re-search-forward "^#\\+COLUMNS:.*" nil t)
904 (setq cnt (1+ cnt))
905 (replace-match (concat "#+COLUMNS: " fmt) t t))
906 (unless (> cnt 0)
907 (goto-char (point-min))
e66ba1df 908 (or (org-at-heading-p t) (outline-next-heading))
20908596
CD
909 (let ((inhibit-read-only t))
910 (insert-before-markers "#+COLUMNS: " fmt "\n")))
911 (org-set-local 'org-columns-default-format fmt))))))
912
20908596
CD
913(defun org-columns-get-autowidth-alist (s cache)
914 "Derive the maximum column widths from the format and the cache."
915 (let ((start 0) rtn)
916 (while (string-match (org-re "%\\([[:alpha:]][[:alnum:]_-]*\\)") s start)
917 (push (cons (match-string 1 s) 1) rtn)
918 (setq start (match-end 0)))
919 (mapc (lambda (x)
920 (setcdr x (apply 'max
921 (mapcar
922 (lambda (y)
923 (length (or (cdr (assoc (car x) (cdr y))) " ")))
924 cache))))
925 rtn)
926 rtn))
927
928(defun org-columns-compute-all ()
929 "Compute all columns that have operators defined."
271672fa 930 (org-with-silent-modifications
20908596 931 (remove-text-properties (point-min) (point-max) '(org-summaries t)))
8bfe682a
CD
932 (let ((columns org-columns-current-fmt-compiled)
933 (org-columns-time (time-to-number-of-days (current-time)))
934 col)
20908596
CD
935 (while (setq col (pop columns))
936 (when (nth 3 col)
937 (save-excursion
938 (org-columns-compute (car col)))))))
939
940(defun org-columns-update (property)
941 "Recompute PROPERTY, and update the columns display for it."
942 (org-columns-compute property)
943 (let (fmt val pos)
944 (save-excursion
945 (mapc (lambda (ov)
86fbb8ca
CD
946 (when (equal (overlay-get ov 'org-columns-key) property)
947 (setq pos (overlay-start ov))
20908596
CD
948 (goto-char pos)
949 (when (setq val (cdr (assoc property
950 (get-text-property
951 (point-at-bol) 'org-summaries))))
86fbb8ca
CD
952 (setq fmt (overlay-get ov 'org-columns-format))
953 (overlay-put ov 'org-columns-value val)
954 (overlay-put ov 'display (format fmt val)))))
20908596
CD
955 org-columns-overlays))))
956
153ae947
BG
957(defvar org-inlinetask-min-level
958 (if (featurep 'org-inlinetask) org-inlinetask-min-level 15))
73d3db82
BG
959
960;;;###autoload
20908596
CD
961(defun org-columns-compute (property)
962 "Sum the values of property PROPERTY hierarchically, for the entire buffer."
963 (interactive)
3ab2c837 964 (let* ((re org-outline-regexp-bol)
20908596 965 (lmax 30) ; Does anyone use deeper levels???
c8d0cf5c 966 (lvals (make-vector lmax nil))
20908596
CD
967 (lflag (make-vector lmax nil))
968 (level 0)
969 (ass (assoc property org-columns-current-fmt-compiled))
970 (format (nth 4 ass))
971 (printf (nth 5 ass))
c8d0cf5c 972 (fun (nth 6 ass))
8bfe682a 973 (calc (or (nth 7 ass) 'identity))
20908596 974 (beg org-columns-top-level-marker)
153ae947
BG
975 (inminlevel org-inlinetask-min-level)
976 (last-level org-inlinetask-min-level)
977 val valflag flag end sumpos sum-alist sum str str1 useval)
20908596
CD
978 (save-excursion
979 ;; Find the region to compute
980 (goto-char beg)
981 (setq end (condition-case nil (org-end-of-subtree t) (error (point-max))))
982 (goto-char end)
983 ;; Walk the tree from the back and do the computations
984 (while (re-search-backward re beg t)
985 (setq sumpos (match-beginning 0)
153ae947
BG
986 last-level (if (not (or (zerop level) (eq level inminlevel)))
987 level last-level)
20908596
CD
988 level (org-outline-level)
989 val (org-entry-get nil property)
990 valflag (and val (string-match "\\S-" val)))
991 (cond
992 ((< level last-level)
993 ;; put the sum of lower levels here as a property
153ae947
BG
994 (setq sum (+ (if (and (/= last-level inminlevel)
995 (aref lvals last-level))
996 (apply fun (aref lvals last-level)) 0)
997 (if (aref lvals inminlevel)
998 (apply fun (aref lvals inminlevel)) 0))
999 flag (or (aref lflag last-level) ; any valid entries from children?
1000 (aref lflag inminlevel)) ; or inline tasks?
20908596
CD
1001 str (org-columns-number-to-string sum format printf)
1002 str1 (org-add-props (copy-sequence str) nil 'org-computed t 'face 'bold)
1003 useval (if flag str1 (if valflag val ""))
1004 sum-alist (get-text-property sumpos 'org-summaries))
1005 (if (assoc property sum-alist)
1006 (setcdr (assoc property sum-alist) useval)
1007 (push (cons property useval) sum-alist)
271672fa 1008 (org-with-silent-modifications
20908596
CD
1009 (add-text-properties sumpos (1+ sumpos)
1010 (list 'org-summaries sum-alist))))
1011 (when (and val (not (equal val (if flag str val))))
1012 (org-entry-put nil property (if flag str val)))
8bfe682a 1013 ;; add current to current level accumulator
20908596 1014 (when (or flag valflag)
8bfe682a
CD
1015 (push (if flag
1016 sum
1017 (funcall calc (org-columns-string-to-number
1018 (if flag str val) format)))
c8d0cf5c 1019 (aref lvals level))
20908596
CD
1020 (aset lflag level t))
1021 ;; clear accumulators for deeper levels
1022 (loop for l from (1+ level) to (1- lmax) do
c8d0cf5c 1023 (aset lvals l nil)
20908596
CD
1024 (aset lflag l nil)))
1025 ((>= level last-level)
1026 ;; add what we have here to the accumulator for this level
c8d0cf5c 1027 (when valflag
8bfe682a
CD
1028 (push (funcall calc (org-columns-string-to-number val format))
1029 (aref lvals level))
c8d0cf5c 1030 (aset lflag level t)))
20908596
CD
1031 (t (error "This should not happen")))))))
1032
1033(defun org-columns-redo ()
1034 "Construct the column display again."
1035 (interactive)
1036 (message "Recomputing columns...")
b349f79f
CD
1037 (let ((line (org-current-line))
1038 (col (current-column)))
1039 (save-excursion
1040 (if (marker-position org-columns-begin-marker)
1041 (goto-char org-columns-begin-marker))
1042 (org-columns-remove-overlays)
8223b1d2 1043 (if (derived-mode-p 'org-mode)
b349f79f
CD
1044 (call-interactively 'org-columns)
1045 (org-agenda-redo)
1046 (call-interactively 'org-agenda-columns)))
54a0dee5 1047 (org-goto-line line)
b349f79f 1048 (move-to-column col))
20908596
CD
1049 (message "Recomputing columns...done"))
1050
1051(defun org-columns-not-in-agenda ()
1052 (if (eq major-mode 'org-agenda-mode)
1053 (error "This command is only allowed in Org-mode buffers")))
1054
20908596
CD
1055(defun org-string-to-number (s)
1056 "Convert string to number, and interpret hh:mm:ss."
1057 (if (not (string-match ":" s))
1058 (string-to-number s)
1059 (let ((l (nreverse (org-split-string s ":"))) (sum 0.0))
1060 (while l
1061 (setq sum (+ (string-to-number (pop l)) (/ sum 60))))
1062 sum)))
1063
73d3db82 1064;;;###autoload
20908596
CD
1065(defun org-columns-number-to-string (n fmt &optional printf)
1066 "Convert a computed column number to a string value, according to FMT."
1067 (cond
afe98dfa 1068 ((memq fmt '(estimate)) (org-estimate-print n printf))
c8d0cf5c
CD
1069 ((not (numberp n)) "")
1070 ((memq fmt '(add_times max_times min_times mean_times))
271672fa 1071 (org-hours-to-clocksum-string n))
20908596
CD
1072 ((eq fmt 'checkbox)
1073 (cond ((= n (floor n)) "[X]")
1074 ((> n 1.) "[-]")
1075 (t "[ ]")))
1076 ((memq fmt '(checkbox-n-of-m checkbox-percent))
1077 (let* ((n1 (floor n)) (n2 (floor (+ .5 (* 1000000 (- n n1))))))
1078 (org-nofm-to-completion n1 (+ n2 n1) (eq fmt 'checkbox-percent))))
1079 (printf (format printf n))
1080 ((eq fmt 'currency)
1081 (format "%.2f" n))
8bfe682a
CD
1082 ((memq fmt '(min_age max_age mean_age))
1083 (org-format-time-period n))
20908596
CD
1084 (t (number-to-string n))))
1085
1086(defun org-nofm-to-completion (n m &optional percent)
1087 (if (not percent)
1088 (format "[%d/%d]" n m)
1089 (format "[%d%%]"(floor (+ 0.5 (* 100. (/ (* 1.0 n) m)))))))
1090
afe98dfa 1091
8bfe682a 1092(defun org-columns-string-to-number (s fmt)
20908596 1093 "Convert a column value to a number that can be used for column computing."
8bfe682a
CD
1094 (if s
1095 (cond
1096 ((memq fmt '(min_age max_age mean_age))
afe98dfa
CD
1097 (cond ((string= s "") org-columns-time)
1098 ((string-match
1099 "\\([0-9]+\\)d \\([0-9]+\\)h \\([0-9]+\\)m \\([0-9]+\\)s"
1100 s)
1101 (+ (* 60 (+ (* 60 (+ (* 24 (string-to-number (match-string 1 s)))
1102 (string-to-number (match-string 2 s))))
1103 (string-to-number (match-string 3 s))))
1104 (string-to-number (match-string 4 s))))
1105 (t (time-to-number-of-days (apply 'encode-time
1106 (org-parse-time-string s t))))))
8bfe682a 1107 ((string-match ":" s)
afe98dfa
CD
1108 (let ((l (nreverse (org-split-string s ":"))) (sum 0.0))
1109 (while l
1110 (setq sum (+ (string-to-number (pop l)) (/ sum 60))))
1111 sum))
8223b1d2
BG
1112 ((string-match (concat "\\([0-9.]+\\) *\\("
1113 (regexp-opt (mapcar 'car org-effort-durations))
1114 "\\)") s)
1115 (setq s (concat "0:" (org-duration-string-to-minutes s t)))
1116 (let ((l (nreverse (org-split-string s ":"))) (sum 0.0))
1117 (while l
1118 (setq sum (+ (string-to-number (pop l)) (/ sum 60))))
1119 sum))
8bfe682a 1120 ((memq fmt '(checkbox checkbox-n-of-m checkbox-percent))
afe98dfa
CD
1121 (if (equal s "[X]") 1. 0.000001))
1122 ((memq fmt '(estimate)) (org-string-to-estimate s))
8bfe682a 1123 (t (string-to-number s)))))
20908596
CD
1124
1125(defun org-columns-uncompile-format (cfmt)
1126 "Turn the compiled columns format back into a string representation."
8bfe682a 1127 (let ((rtn "") e s prop title op op-match width fmt printf fun calc)
20908596
CD
1128 (while (setq e (pop cfmt))
1129 (setq prop (car e)
1130 title (nth 1 e)
1131 width (nth 2 e)
1132 op (nth 3 e)
1133 fmt (nth 4 e)
c8d0cf5c 1134 printf (nth 5 e)
8bfe682a
CD
1135 fun (nth 6 e)
1136 calc (nth 7 e))
1137 (when (setq op-match (rassoc (list fmt fun calc) org-columns-compile-map))
c8d0cf5c 1138 (setq op (car op-match)))
20908596
CD
1139 (if (and op printf) (setq op (concat op ";" printf)))
1140 (if (equal title prop) (setq title nil))
1141 (setq s (concat "%" (if width (number-to-string width))
1142 prop
1143 (if title (concat "(" title ")"))
1144 (if op (concat "{" op "}"))))
1145 (setq rtn (concat rtn " " s)))
1146 (org-trim rtn)))
1147
1148(defun org-columns-compile-format (fmt)
1149 "Turn a column format string into an alist of specifications.
1150The alist has one entry for each column in the format. The elements of
1151that list are:
1152property the property
1153title the title field for the columns
1154width the column width in characters, can be nil for automatic
1155operator the operator if any
1156format the output format for computed results, derived from operator
c8d0cf5c 1157printf a printf format for computed values
8bfe682a 1158fun the lisp function to compute summary values, derived from operator
86fbb8ca 1159calc function to get values from base elements"
8bfe682a 1160 (let ((start 0) width prop title op op-match f printf fun calc)
20908596
CD
1161 (setq org-columns-current-fmt-compiled nil)
1162 (while (string-match
1163 (org-re "%\\([0-9]+\\)?\\([[:alnum:]_-]+\\)\\(?:(\\([^)]+\\))\\)?\\(?:{\\([^}]+\\)}\\)?\\s-*")
1164 fmt start)
1165 (setq start (match-end 0)
1166 width (match-string 1 fmt)
1167 prop (match-string 2 fmt)
1168 title (or (match-string 3 fmt) prop)
1169 op (match-string 4 fmt)
1170 f nil
c8d0cf5c 1171 printf nil
8bfe682a
CD
1172 fun '+
1173 calc nil)
20908596
CD
1174 (if width (setq width (string-to-number width)))
1175 (when (and op (string-match ";" op))
1176 (setq printf (substring op (match-end 0))
1177 op (substring op 0 (match-beginning 0))))
c8d0cf5c
CD
1178 (when (setq op-match (assoc op org-columns-compile-map))
1179 (setq f (cadr op-match)
8bfe682a
CD
1180 fun (caddr op-match)
1181 calc (cadddr op-match)))
1182 (push (list prop title width op f printf fun calc)
1183 org-columns-current-fmt-compiled))
20908596
CD
1184 (setq org-columns-current-fmt-compiled
1185 (nreverse org-columns-current-fmt-compiled))))
1186
1187
1188;;; Dynamic block for Column view
1189
e66ba1df
BG
1190(defvar org-heading-regexp) ; defined in org.el
1191(defvar org-heading-keyword-regexp-format) ; defined in org.el
20908596
CD
1192(defun org-columns-capture-view (&optional maxlevel skip-empty-rows)
1193 "Get the column view of the current buffer or subtree.
1194The first optional argument MAXLEVEL sets the level limit. A
1195second optional argument SKIP-EMPTY-ROWS tells whether to skip
1196empty rows, an empty row being one where all the column view
1197specifiers except ITEM are empty. This function returns a list
1198containing the title row and all other rows. Each row is a list
1199of fields."
1200 (save-excursion
1201 (let* ((title (mapcar 'cadr org-columns-current-fmt-compiled))
e66ba1df
BG
1202 (re-comment (format org-heading-keyword-regexp-format
1203 org-comment-string))
c8d0cf5c 1204 (re-archive (concat ".*:" org-archive-tag ":"))
20908596
CD
1205 (n (length title)) row tbl)
1206 (goto-char (point-min))
e66ba1df 1207 (while (re-search-forward org-heading-regexp nil t)
c8d0cf5c
CD
1208 (catch 'next
1209 (when (and (or (null maxlevel)
1210 (>= maxlevel
1211 (if org-odd-levels-only
1212 (/ (1+ (length (match-string 1))) 2)
1213 (length (match-string 1)))))
1214 (get-char-property (match-beginning 0) 'org-columns-key))
1215 (when (save-excursion
1216 (goto-char (point-at-bol))
1217 (or (looking-at re-comment)
1218 (looking-at re-archive)))
1219 (org-end-of-subtree t)
1220 (throw 'next t))
1221 (setq row nil)
1222 (loop for i from 0 to (1- n) do
1223 (push
1224 (org-quote-vert
1225 (or (get-char-property (+ (match-beginning 0) i) 'org-columns-value-modified)
1226 (get-char-property (+ (match-beginning 0) i) 'org-columns-value)
1227 ""))
1228 row))
1229 (setq row (nreverse row))
1230 (unless (and skip-empty-rows
1231 (eq 1 (length (delete "" (delete-dups (copy-sequence row))))))
1232 (push row tbl)))))
20908596
CD
1233 (append (list title 'hline) (nreverse tbl)))))
1234
bdebdb64 1235;;;###autoload
20908596
CD
1236(defun org-dblock-write:columnview (params)
1237 "Write the column view table.
1238PARAMS is a property list of parameters:
1239
1240:width enforce same column widths with <N> specifiers.
1241:id the :ID: property of the entry where the columns view
8bfe682a
CD
1242 should be built. When the symbol `local', call locally.
1243 When `global' call column view with the cursor at the beginning
1244 of the buffer (usually this means that the whole buffer switches
1245 to column view). When \"file:path/to/file.org\", invoke column
1246 view at the start of that file. Otherwise, the ID is located
1247 using `org-id-find'.
20908596 1248:hlines When t, insert a hline before each item. When a number, insert
8bfe682a 1249 a hline before each level <= that number.
20908596
CD
1250:vlines When t, make each column a colgroup to enforce vertical lines.
1251:maxlevel When set to a number, don't capture headlines below this level.
1252:skip-empty-rows
8223b1d2
BG
1253 When t, skip rows where all specifiers other than ITEM are empty.
1254:format When non-nil, specify the column view format to use."
c7cf0ebc 1255 (let ((pos (point-marker))
20908596
CD
1256 (hlines (plist-get params :hlines))
1257 (vlines (plist-get params :vlines))
1258 (maxlevel (plist-get params :maxlevel))
621f83e4 1259 (content-lines (org-split-string (plist-get params :content) "\n"))
20908596 1260 (skip-empty-rows (plist-get params :skip-empty-rows))
8223b1d2
BG
1261 (columns-fmt (plist-get params :format))
1262 (case-fold-search t)
0bd48b37
CD
1263 tbl id idpos nfields tmp recalc line
1264 id-as-string view-file view-pos)
1265 (when (setq id (plist-get params :id))
1266 (setq id-as-string (cond ((numberp id) (number-to-string id))
1267 ((symbolp id) (symbol-name id))
1268 ((stringp id) id)
1269 (t "")))
1270 (cond ((not id) nil)
1271 ((eq id 'global) (setq view-pos (point-min)))
1272 ((eq id 'local))
1273 ((string-match "^file:\\(.*\\)" id-as-string)
1274 (setq view-file (match-string 1 id-as-string)
1275 view-pos 1)
1276 (unless (file-exists-p view-file)
1277 (error "No such file: \"%s\"" id-as-string)))
1278 ((setq idpos (org-find-entry-with-id id))
1279 (setq view-pos idpos))
1280 ((setq idpos (org-id-find id))
1281 (setq view-file (car idpos))
1282 (setq view-pos (cdr idpos)))
1283 (t (error "Cannot find entry with :ID: %s" id))))
1284 (with-current-buffer (if view-file
1285 (get-file-buffer view-file)
1286 (current-buffer))
1287 (save-excursion
1288 (save-restriction
1289 (widen)
1290 (goto-char (or view-pos (point)))
8223b1d2 1291 (org-columns columns-fmt)
0bd48b37
CD
1292 (setq tbl (org-columns-capture-view maxlevel skip-empty-rows))
1293 (setq nfields (length (car tbl)))
1294 (org-columns-quit))))
20908596
CD
1295 (goto-char pos)
1296 (move-marker pos nil)
1297 (when tbl
1298 (when (plist-get params :hlines)
1299 (setq tmp nil)
1300 (while tbl
1301 (if (eq (car tbl) 'hline)
1302 (push (pop tbl) tmp)
1303 (if (string-match "\\` *\\(\\*+\\)" (caar tbl))
1304 (if (and (not (eq (car tmp) 'hline))
1305 (or (eq hlines t)
0bd48b37
CD
1306 (and (numberp hlines)
1307 (<= (- (match-end 1) (match-beginning 1))
1308 hlines))))
20908596
CD
1309 (push 'hline tmp)))
1310 (push (pop tbl) tmp)))
1311 (setq tbl (nreverse tmp)))
1312 (when vlines
1313 (setq tbl (mapcar (lambda (x)
1314 (if (eq 'hline x) x (cons "" x)))
1315 tbl))
1316 (setq tbl (append tbl (list (cons "/" (make-list nfields "<>"))))))
621f83e4
CD
1317 (when content-lines
1318 (while (string-match "^#" (car content-lines))
1319 (insert (pop content-lines) "\n")))
271672fa 1320 (setq pos (point))
20908596
CD
1321 (insert (org-listtable-to-string tbl))
1322 (when (plist-get params :width)
1323 (insert "\n|" (mapconcat (lambda (x) (format "<%d>" (max 3 x)))
1324 org-columns-current-widths "|")))
621f83e4
CD
1325 (while (setq line (pop content-lines))
1326 (when (string-match "^#" line)
1327 (insert "\n" line)
8223b1d2 1328 (when (string-match "^[ \t]*#\\+tblfm" line)
621f83e4
CD
1329 (setq recalc t))))
1330 (if recalc
1331 (progn (goto-char pos) (org-table-recalculate 'all))
1332 (goto-char pos)
1333 (org-table-align)))))
20908596
CD
1334
1335(defun org-listtable-to-string (tbl)
1336 "Convert a listtable TBL to a string that contains the Org-mode table.
33306645 1337The table still need to be aligned. The resulting string has no leading
20908596
CD
1338and tailing newline characters."
1339 (mapconcat
1340 (lambda (x)
1341 (cond
1342 ((listp x)
1343 (concat "|" (mapconcat 'identity x "|") "|"))
1344 ((eq x 'hline) "|-|")
1345 (t (error "Garbage in listtable: %s" x))))
1346 tbl "\n"))
1347
bdebdb64 1348;;;###autoload
20908596
CD
1349(defun org-insert-columns-dblock ()
1350 "Create a dynamic block capturing a column view table."
1351 (interactive)
1352 (let ((defaults '(:name "columnview" :hlines 1))
54a0dee5 1353 (id (org-icompleting-read
20908596
CD
1354 "Capture columns (local, global, entry with :ID: property) [local]: "
1355 (append '(("global") ("local"))
1356 (mapcar 'list (org-property-values "ID"))))))
1357 (if (equal id "") (setq id 'local))
1358 (if (equal id "global") (setq id 'global))
1359 (setq defaults (append defaults (list :id id)))
1360 (org-create-dblock defaults)
1361 (org-update-dblock)))
1362
1363;;; Column view in the agenda
1364
1365(defvar org-agenda-view-columns-initially nil
1366 "When set, switch to columns view immediately after creating the agenda.")
1367
1368(defvar org-agenda-columns-show-summaries) ; defined in org-agenda.el
1369(defvar org-agenda-columns-compute-summary-properties); defined in org-agenda.el
1370(defvar org-agenda-columns-add-appointments-to-effort-sum); as well
1371
bdebdb64 1372;;;###autoload
20908596
CD
1373(defun org-agenda-columns ()
1374 "Turn on or update column view in the agenda."
1375 (interactive)
1376 (org-verify-version 'columns)
1377 (org-columns-remove-overlays)
1378 (move-marker org-columns-begin-marker (point))
8bfe682a 1379 (let ((org-columns-time (time-to-number-of-days (current-time)))
8223b1d2 1380 cache maxwidths m p a d fmt)
20908596
CD
1381 (cond
1382 ((and (boundp 'org-agenda-overriding-columns-format)
1383 org-agenda-overriding-columns-format)
8223b1d2 1384 (setq fmt org-agenda-overriding-columns-format))
8d642074 1385 ((setq m (org-get-at-bol 'org-hd-marker))
20908596
CD
1386 (setq fmt (or (org-entry-get m "COLUMNS" t)
1387 (with-current-buffer (marker-buffer m)
1388 org-columns-default-format))))
1389 ((and (boundp 'org-columns-current-fmt)
1390 (local-variable-p 'org-columns-current-fmt)
1391 org-columns-current-fmt)
1392 (setq fmt org-columns-current-fmt))
1393 ((setq m (next-single-property-change (point-min) 'org-hd-marker))
1394 (setq m (get-text-property m 'org-hd-marker))
1395 (setq fmt (or (org-entry-get m "COLUMNS" t)
1396 (with-current-buffer (marker-buffer m)
1397 org-columns-default-format)))))
1398 (setq fmt (or fmt org-columns-default-format))
1399 (org-set-local 'org-columns-current-fmt fmt)
1400 (org-columns-compile-format fmt)
1401 (when org-agenda-columns-compute-summary-properties
1402 (org-agenda-colview-compute org-columns-current-fmt-compiled))
1403 (save-excursion
1404 ;; Get and cache the properties
1405 (goto-char (point-min))
1406 (while (not (eobp))
8d642074
CD
1407 (when (setq m (or (org-get-at-bol 'org-hd-marker)
1408 (org-get-at-bol 'org-marker)))
20908596
CD
1409 (setq p (org-entry-properties m))
1410
1411 (when (or (not (setq a (assoc org-effort-property p)))
8223b1d2 1412 (not (string-match "\\S-" (or (cdr a) ""))))
20908596
CD
1413 ;; OK, the property is not defined. Use appointment duration?
1414 (when (and org-agenda-columns-add-appointments-to-effort-sum
1415 (setq d (get-text-property (point) 'duration)))
271672fa 1416 (setq d (org-minutes-to-clocksum-string d))
20908596
CD
1417 (put-text-property 0 (length d) 'face 'org-warning d)
1418 (push (cons org-effort-property d) p)))
1419 (push (cons (org-current-line) p) cache))
1420 (beginning-of-line 2))
1421 (when cache
1422 (setq maxwidths (org-columns-get-autowidth-alist fmt cache))
1423 (org-set-local 'org-columns-current-maxwidths maxwidths)
1424 (org-columns-display-here-title)
1425 (when (org-set-local 'org-columns-flyspell-was-active
1426 (org-bound-and-true-p flyspell-mode))
1427 (flyspell-mode 0))
1428 (mapc (lambda (x)
54a0dee5 1429 (org-goto-line (car x))
20908596
CD
1430 (org-columns-display-here (cdr x)))
1431 cache)
1432 (when org-agenda-columns-show-summaries
1433 (org-agenda-colview-summarize cache))))))
1434
1435(defun org-agenda-colview-summarize (cache)
1436 "Summarize the summarizable columns in column view in the agenda.
1437This will add overlays to the date lines, to show the summary for each day."
1438 (let* ((fmt (mapcar (lambda (x)
8223b1d2
BG
1439 (if (string-match "CLOCKSUM.*" (car x))
1440 (list (match-string 0 (car x))
1441 (nth 1 x) (nth 2 x) ":" 'add_times
ed21c5c8
CD
1442 nil '+ nil)
1443 x))
20908596 1444 org-columns-current-fmt-compiled))
ed21c5c8 1445 line c c1 stype calc sumfunc props lsum entries prop v title)
20908596
CD
1446 (catch 'exit
1447 (when (delq nil (mapcar 'cadr fmt))
1448 ;; OK, at least one summation column, it makes sense to try this
1449 (goto-char (point-max))
1450 (while t
1451 (when (or (get-text-property (point) 'org-date-line)
1452 (eq (get-text-property (point) 'face)
1453 'org-agenda-structure))
1454 ;; OK, this is a date line that should be used
1455 (setq line (org-current-line))
1456 (setq entries nil c cache cache nil)
1457 (while (setq c1 (pop c))
1458 (if (> (car c1) line)
1459 (push c1 entries)
1460 (push c1 cache)))
1461 ;; now ENTRIES are the ones we want to use, CACHE is the rest
1462 ;; Compute the summaries for the properties we want,
1463 ;; set nil properties for the rest.
1464 (when (setq entries (mapcar 'cdr entries))
1465 (setq props
1466 (mapcar
1467 (lambda (f)
8bfe682a 1468 (setq prop (car f)
ed21c5c8
CD
1469 title (nth 1 f)
1470 stype (nth 4 f)
1471 sumfunc (nth 6 f)
1472 calc (or (nth 7 f) 'identity))
20908596
CD
1473 (cond
1474 ((equal prop "ITEM")
1475 (cons prop (buffer-substring (point-at-bol)
1476 (point-at-eol))))
1477 ((not stype) (cons prop ""))
8bfe682a
CD
1478 (t ;; do the summary
1479 (setq lsum nil)
1480 (dolist (x entries)
1481 (setq v (cdr (assoc prop x)))
1482 (if v
1483 (push
1484 (funcall
1485 (if (not (get-text-property 0 'org-computed v))
1486 calc
1487 'identity)
1488 (org-columns-string-to-number
1489 v stype))
1490 lsum)))
1491 (setq lsum (remove nil lsum))
1492 (setq lsum
1493 (cond ((> (length lsum) 1)
1494 (org-columns-number-to-string
1495 (apply sumfunc lsum) stype))
1496 ((eq (length lsum) 1)
1497 (org-columns-number-to-string
1498 (car lsum) stype))
1499 (t "")))
1500 (put-text-property 0 (length lsum) 'face 'bold lsum)
1501 (unless (eq calc 'identity)
1502 (put-text-property 0 (length lsum) 'org-computed t lsum))
20908596
CD
1503 (cons prop lsum))))
1504 fmt))
ce4fdcb9 1505 (org-columns-display-here props 'dateline)
20908596
CD
1506 (org-set-local 'org-agenda-columns-active t)))
1507 (if (bobp) (throw 'exit t))
1508 (beginning-of-line 0))))))
1509
1510(defun org-agenda-colview-compute (fmt)
1511 "Compute the relevant columns in the contributing source buffers."
1512 (let ((files org-agenda-contributing-files)
1513 (org-columns-begin-marker (make-marker))
1514 (org-columns-top-level-marker (make-marker))
1515 f fm a b)
1516 (while (setq f (pop files))
1517 (setq b (find-buffer-visiting f))
1518 (with-current-buffer (or (buffer-base-buffer b) b)
1519 (save-excursion
1520 (save-restriction
1521 (widen)
271672fa
BG
1522 (org-with-silent-modifications
1523 (remove-text-properties (point-min) (point-max) '(org-summaries t)))
20908596
CD
1524 (goto-char (point-min))
1525 (org-columns-get-format-and-top-level)
1526 (while (setq fm (pop fmt))
8223b1d2
BG
1527 (cond ((equal (car fm) "CLOCKSUM")
1528 (org-clock-sum))
1529 ((equal (car fm) "CLOCKSUM_T")
1530 (org-clock-sum-today))
1531 ((and (nth 4 fm)
1532 (setq a (assoc (car fm)
1533 org-columns-current-fmt-compiled))
1534 (equal (nth 4 a) (nth 4 fm)))
1535 (org-columns-compute (car fm)))))))))))
20908596 1536
8bfe682a 1537(defun org-format-time-period (interval)
86fbb8ca 1538 "Convert time in fractional days to days/hours/minutes/seconds."
8bfe682a 1539 (if (numberp interval)
8223b1d2
BG
1540 (let* ((days (floor interval))
1541 (frac-hours (* 24 (- interval days)))
1542 (hours (floor frac-hours))
1543 (minutes (floor (* 60 (- frac-hours hours))))
1544 (seconds (floor (* 60 (- (* 60 (- frac-hours hours)) minutes)))))
1545 (format "%dd %02dh %02dm %02ds" days hours minutes seconds))
8bfe682a
CD
1546 ""))
1547
afe98dfa
CD
1548(defun org-estimate-mean-and-var (v)
1549 "Return the mean and variance of an estimate."
1550 (let* ((low (float (car v)))
1551 (high (float (cadr v)))
1552 (mean (/ (+ low high) 2.0))
1553 (var (/ (+ (expt (- mean low) 2.0) (expt (- high mean) 2.0)) 2.0)))
1554 (list mean var)))
1555
1556(defun org-estimate-combine (&rest el)
1557 "Combine a list of estimates, using mean and variance.
1558The mean and variance of the result will be the sum of the means
1559and variances (respectively) of the individual estimates."
1560 (let ((mean 0)
1561 (var 0))
1562 (mapc (lambda (e)
8223b1d2
BG
1563 (let ((stats (org-estimate-mean-and-var e)))
1564 (setq mean (+ mean (car stats)))
1565 (setq var (+ var (cadr stats)))))
1566 el)
afe98dfa
CD
1567 (let ((stdev (sqrt var)))
1568 (list (- mean stdev) (+ mean stdev)))))
1569
1570(defun org-estimate-print (e &optional fmt)
1571 "Prepare a string representation of an estimate.
1572This formats these numbers as two numbers with a \"-\" between them."
1573 (if (null fmt) (set 'fmt "%.0f"))
1574 (format "%s" (mapconcat (lambda (n) (format fmt n)) e "-")))
1575
1576(defun org-string-to-estimate (s)
1577 "Convert a string to an estimate.
1578The string should be two numbers joined with a \"-\"."
1579 (if (string-match "\\(.*\\)-\\(.*\\)" s)
1580 (list (string-to-number (match-string 1 s))
1581 (string-to-number(match-string 2 s)))
1582 (list (string-to-number s) (string-to-number s))))
8bfe682a 1583
20908596
CD
1584(provide 'org-colview)
1585
b349f79f 1586;;; org-colview.el ends here