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