Merge upstream Org (from commit 488eea)
[bpt/emacs.git] / lisp / org / org-timer.el
CommitLineData
0bd48b37 1;;; org-timer.el --- The relative timer code for Org-mode
bc23baaa 2
b73f1974 3;; Copyright (C) 2008-2012 Free Software Foundation, Inc.
bc23baaa
CD
4
5;; Author: Carsten Dominik <carsten at orgmode dot org>
6;; Keywords: outlines, hypermedia, calendar, wp
7;; Homepage: http://orgmode.org
bc23baaa
CD
8;;
9;; This file is part of GNU Emacs.
10;;
11;; GNU Emacs is free software: you can redistribute it and/or modify
12;; it under the terms of the GNU General Public License as published by
13;; the Free Software Foundation, either version 3 of the License, or
14;; (at your option) any later version.
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
22;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
23;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
24;;
25;;; Commentary:
26
27;; This file contains the relative timer code for Org-mode
28
86fbb8ca
CD
29;;; Code:
30
bc23baaa
CD
31(require 'org)
32
afe98dfa 33(declare-function org-notify "org-clock" (notification &optional play-sound))
c8d0cf5c
CD
34(declare-function org-agenda-error "org-agenda" ())
35
bc23baaa
CD
36(defvar org-timer-start-time nil
37 "t=0 for the running timer.")
38
0bd48b37
CD
39(defvar org-timer-pause-time nil
40 "Time when the timer was paused.")
41
bc23baaa
CD
42(defconst org-timer-re "\\([-+]?[0-9]+\\):\\([0-9]\\{2\\}\\):\\([0-9]\\{2\\}\\)"
43 "Regular expression used to match timer stamps.")
44
45(defcustom org-timer-format "%s "
46 "The format to insert the time of the timer.
47This format must contain one instance of \"%s\" which will be replaced by
48the value of the relative timer."
49 :group 'org-time
50 :type 'string)
51
86fbb8ca
CD
52(defcustom org-timer-default-timer 0
53 "The default timer when a timer is set.
54When 0, the user is prompted for a value."
55 :group 'org-time
372d7b21 56 :version "24.1"
86fbb8ca
CD
57 :type 'number)
58
8223b1d2
BG
59(defcustom org-timer-display 'mode-line
60 "When a timer is running, org-mode can display it in the mode
61line and/or frame title.
62Allowed values are:
63
64both displays in both mode line and frame title
65mode-line displays only in mode line (default)
66frame-title displays only in frame title
67nil current timer is not displayed"
68 :group 'org-time
69 :type '(choice
70 (const :tag "Mode line" mode-line)
71 (const :tag "Frame title" frame-title)
72 (const :tag "Both" both)
73 (const :tag "None" nil)))
74
ed21c5c8
CD
75(defvar org-timer-start-hook nil
76 "Hook run after relative timer is started.")
77
78(defvar org-timer-stop-hook nil
79 "Hook run before relative timer is stopped.")
80
81(defvar org-timer-pause-hook nil
82 "Hook run before relative timer is paused.")
83
3ab2c837 84(defvar org-timer-continue-hook nil
8223b1d2 85 "Hook run after relative timer is continued.")
3ab2c837 86
ed21c5c8
CD
87(defvar org-timer-set-hook nil
88 "Hook run after countdown timer is set.")
89
90(defvar org-timer-done-hook nil
91 "Hook run after countdown timer reaches zero.")
92
93(defvar org-timer-cancel-hook nil
94 "Hook run before countdown timer is canceled.")
95
bc23baaa
CD
96;;;###autoload
97(defun org-timer-start (&optional offset)
98 "Set the starting time for the relative timer to now.
99When called with prefix argument OFFSET, prompt the user for an offset time,
100with the default taken from a timer stamp at point, if any.
101If OFFSET is a string or an integer, it is directly taken to be the offset
102without user interaction.
103When called with a double prefix arg, all timer strings in the active
104region will be shifted by a specific amount. You will be prompted for
105the amount, with the default to make the first timer string in
106the region 0:00:00."
107 (interactive "P")
108 (if (equal offset '(16))
109 (call-interactively 'org-timer-change-times-in-region)
110 (let (delta def s)
111 (if (not offset)
112 (setq org-timer-start-time (current-time))
113 (cond
114 ((integerp offset) (setq delta offset))
115 ((stringp offset) (setq delta (org-timer-hms-to-secs offset)))
116 (t
117 (setq def (if (org-in-regexp org-timer-re)
118 (match-string 0)
119 "0:00:00")
120 s (read-string
121 (format "Restart timer with offset [%s]: " def)))
122 (unless (string-match "\\S-" s) (setq s def))
123 (setq delta (org-timer-hms-to-secs (org-timer-fix-incomplete s)))))
124 (setq org-timer-start-time
125 (seconds-to-time
86fbb8ca 126 (- (org-float-time) delta))))
0bd48b37 127 (org-timer-set-mode-line 'on)
bc23baaa
CD
128 (message "Timer start time set to %s, current value is %s"
129 (format-time-string "%T" org-timer-start-time)
ed21c5c8
CD
130 (org-timer-secs-to-hms (or delta 0)))
131 (run-hooks 'org-timer-start-hook))))
bc23baaa 132
0bd48b37 133(defun org-timer-pause-or-continue (&optional stop)
86fbb8ca
CD
134 "Pause or continue the relative timer.
135With prefix arg STOP, stop it entirely."
0bd48b37
CD
136 (interactive "P")
137 (cond
138 (stop (org-timer-stop))
139 ((not org-timer-start-time) (error "No timer is running"))
140 (org-timer-pause-time
141 ;; timer is paused, continue
142 (setq org-timer-start-time
143 (seconds-to-time
144 (-
54a0dee5
CD
145 (org-float-time)
146 (- (org-float-time org-timer-pause-time)
147 (org-float-time org-timer-start-time))))
0bd48b37
CD
148 org-timer-pause-time nil)
149 (org-timer-set-mode-line 'on)
3ab2c837 150 (run-hooks 'org-timer-continue-hook)
0bd48b37
CD
151 (message "Timer continues at %s" (org-timer-value-string)))
152 (t
153 ;; pause timer
ed21c5c8 154 (run-hooks 'org-timer-pause-hook)
0bd48b37
CD
155 (setq org-timer-pause-time (current-time))
156 (org-timer-set-mode-line 'pause)
157 (message "Timer paused at %s" (org-timer-value-string)))))
158
159(defun org-timer-stop ()
160 "Stop the relative timer."
161 (interactive)
ed21c5c8 162 (run-hooks 'org-timer-stop-hook)
0bd48b37
CD
163 (setq org-timer-start-time nil
164 org-timer-pause-time nil)
63aa0982
BG
165 (org-timer-set-mode-line 'off)
166 (message "Timer stopped"))
0bd48b37 167
bc23baaa 168;;;###autoload
afe98dfa 169(defun org-timer (&optional restart no-insert-p)
bc23baaa
CD
170 "Insert a H:MM:SS string from the timer into the buffer.
171The first time this command is used, the timer is started. When used with
86fbb8ca 172a \\[universal-argument] prefix, force restarting the timer.
afe98dfa 173When used with a double prefix argument \\[universal-argument], change all the timer string
bc23baaa 174in the region by a fixed amount. This can be used to recalibrate a timer
afe98dfa
CD
175that was not started at the correct moment.
176
177If NO-INSERT-P is non-nil, return the string instead of inserting
178it in the buffer."
bc23baaa 179 (interactive "P")
afe98dfa
CD
180 (when (or (equal restart '(4)) (not org-timer-start-time))
181 (org-timer-start))
182 (if no-insert-p
183 (org-timer-value-string)
184 (insert (org-timer-value-string))))
0bd48b37
CD
185
186(defun org-timer-value-string ()
187 (format org-timer-format (org-timer-secs-to-hms (floor (org-timer-seconds)))))
188
afe98dfa 189(defvar org-timer-timer-is-countdown nil)
0bd48b37 190(defun org-timer-seconds ()
afe98dfa
CD
191 (if org-timer-timer-is-countdown
192 (- (org-float-time org-timer-start-time)
193 (org-float-time (current-time)))
194 (- (org-float-time (or org-timer-pause-time (current-time)))
195 (org-float-time org-timer-start-time))))
bc23baaa
CD
196
197;;;###autoload
198(defun org-timer-change-times-in-region (beg end delta)
199 "Change all h:mm:ss time in region by a DELTA."
200 (interactive
8223b1d2 201 "r\nsEnter time difference like \"-1:08:26\". Default is first time to zero: ")
bc23baaa
CD
202 (let ((re "[-+]?[0-9]+:[0-9]\\{2\\}:[0-9]\\{2\\}") p)
203 (unless (string-match "\\S-" delta)
204 (save-excursion
205 (goto-char beg)
206 (when (re-search-forward re end t)
207 (setq delta (match-string 0))
208 (if (equal (string-to-char delta) ?-)
209 (setq delta (substring delta 1))
210 (setq delta (concat "-" delta))))))
211 (setq delta (org-timer-hms-to-secs (org-timer-fix-incomplete delta)))
212 (when (= delta 0) (error "No change"))
213 (save-excursion
214 (goto-char end)
215 (while (re-search-backward re beg t)
216 (setq p (point))
217 (replace-match
218 (save-match-data
219 (org-timer-secs-to-hms (+ (org-timer-hms-to-secs (match-string 0)) delta)))
220 t t)
221 (goto-char p)))))
222
223;;;###autoload
224(defun org-timer-item (&optional arg)
33306645 225 "Insert a description-type item with the current timer value."
bc23baaa 226 (interactive "P")
3ab2c837
BG
227 (let ((itemp (org-in-item-p)) (pos (point)))
228 (cond
229 ;; In a timer list, insert with `org-list-insert-item',
230 ;; then fix the list.
231 ((and itemp (goto-char itemp) (org-at-item-timer-p))
232 (let* ((struct (org-list-struct))
233 (prevs (org-list-prevs-alist struct))
234 (s (concat (org-timer (when arg '(4)) t) ":: ")))
235 (setq struct (org-list-insert-item pos struct prevs nil s))
236 (org-list-write-struct struct (org-list-parents-alist struct))
237 (looking-at org-list-full-item-re)
238 (goto-char (match-end 0))))
239 ;; In a list of another type, don't break anything: throw an error.
240 (itemp (goto-char pos) (error "This is not a timer list"))
241 ;; Else, start a new list.
242 (t
243 (beginning-of-line)
8223b1d2 244 (org-indent-line)
3ab2c837
BG
245 (insert "- ")
246 (org-timer (when arg '(4)))
247 (insert ":: ")))))
bc23baaa
CD
248
249(defun org-timer-fix-incomplete (hms)
250 "If hms is a H:MM:SS string with missing hour or hour and minute, fix it."
251 (if (string-match "\\(?:\\([0-9]+:\\)?\\([0-9]+:\\)\\)?\\([0-9]+\\)" hms)
252 (replace-match
253 (format "%d:%02d:%02d"
254 (if (match-end 1) (string-to-number (match-string 1 hms)) 0)
255 (if (match-end 2) (string-to-number (match-string 2 hms)) 0)
256 (string-to-number (match-string 3 hms)))
257 t t hms)
33306645 258 (error "Cannot parse HMS string \"%s\"" hms)))
bc23baaa
CD
259
260(defun org-timer-hms-to-secs (hms)
261 "Convert h:mm:ss string to an integer time.
262If the string starts with a minus sign, the integer will be negative."
263 (if (not (string-match
264 "\\([-+]?[0-9]+\\):\\([0-9]\\{2\\}\\):\\([0-9]\\{2\\}\\)"
265 hms))
266 0
267 (let* ((h (string-to-number (match-string 1 hms)))
268 (m (string-to-number (match-string 2 hms)))
269 (s (string-to-number (match-string 3 hms)))
270 (sign (equal (substring (match-string 1 hms) 0 1) "-")))
271 (setq h (abs h))
272 (* (if sign -1 1) (+ s (* 60 (+ m (* 60 h))))))))
273
274(defun org-timer-secs-to-hms (s)
275 "Convert integer S into h:mm:ss.
33306645 276If the integer is negative, the string will start with \"-\"."
bc23baaa
CD
277 (let (sign m h)
278 (setq sign (if (< s 0) "-" "")
279 s (abs s)
280 m (/ s 60) s (- s (* 60 m))
281 h (/ m 60) m (- m (* 60 h)))
282 (format "%s%d:%02d:%02d" sign h m s)))
283
0bd48b37
CD
284(defvar org-timer-mode-line-timer nil)
285(defvar org-timer-mode-line-string nil)
286
287(defun org-timer-set-mode-line (value)
8bfe682a 288 "Set the mode-line display of the relative timer.
0bd48b37 289VALUE can be `on', `off', or `pause'."
8223b1d2
BG
290 (when (or (eq org-timer-display 'mode-line)
291 (eq org-timer-display 'both))
292 (or global-mode-string (setq global-mode-string '("")))
293 (or (memq 'org-timer-mode-line-string global-mode-string)
294 (setq global-mode-string
295 (append global-mode-string '(org-timer-mode-line-string)))))
296 (when (or (eq org-timer-display 'frame-title)
297 (eq org-timer-display 'both))
298 (or (memq 'org-timer-mode-line-string frame-title-format)
299 (setq frame-title-format
300 (append frame-title-format '(org-timer-mode-line-string)))))
0bd48b37
CD
301 (cond
302 ((equal value 'off)
303 (when org-timer-mode-line-timer
304 (cancel-timer org-timer-mode-line-timer)
305 (setq org-timer-mode-line-timer nil))
8223b1d2
BG
306 (when (or (eq org-timer-display 'mode-line)
307 (eq org-timer-display 'both))
308 (setq global-mode-string
309 (delq 'org-timer-mode-line-string global-mode-string)))
310 (when (or (eq org-timer-display 'frame-title)
311 (eq org-timer-display 'both))
312 (setq frame-title-format
313 (delq 'org-timer-mode-line-string frame-title-format)))
0bd48b37
CD
314 (force-mode-line-update))
315 ((equal value 'pause)
316 (when org-timer-mode-line-timer
317 (cancel-timer org-timer-mode-line-timer)
318 (setq org-timer-mode-line-timer nil)))
319 ((equal value 'on)
8223b1d2
BG
320 (when (or (eq org-timer-display 'mode-line)
321 (eq org-timer-display 'both))
322 (or global-mode-string (setq global-mode-string '("")))
323 (or (memq 'org-timer-mode-line-string global-mode-string)
324 (setq global-mode-string
325 (append global-mode-string '(org-timer-mode-line-string)))))
326 (when (or (eq org-timer-display 'frame-title)
327 (eq org-timer-display 'both))
328 (or (memq 'org-timer-mode-line-string frame-title-format)
329 (setq frame-title-format
330 (append frame-title-format '(org-timer-mode-line-string)))))
0bd48b37
CD
331 (org-timer-update-mode-line)
332 (when org-timer-mode-line-timer
8223b1d2
BG
333 (cancel-timer org-timer-mode-line-timer)
334 (setq org-timer-mode-line-timer nil))
335 (when org-timer-display
336 (setq org-timer-mode-line-timer
337 (run-with-timer 1 1 'org-timer-update-mode-line))))))
0bd48b37
CD
338
339(defun org-timer-update-mode-line ()
340 "Update the timer time in the mode line."
341 (if org-timer-pause-time
342 nil
343 (setq org-timer-mode-line-string
344 (concat " <" (substring (org-timer-value-string) 0 -1) ">"))
345 (force-mode-line-update)))
346
ed21c5c8
CD
347(defvar org-timer-current-timer nil)
348(defun org-timer-cancel-timer ()
349 "Cancel the current timer."
c8d0cf5c 350 (interactive)
ed21c5c8
CD
351 (when (eval org-timer-current-timer)
352 (run-hooks 'org-timer-cancel-hook)
353 (cancel-timer org-timer-current-timer)
afe98dfa
CD
354 (setq org-timer-current-timer nil)
355 (setq org-timer-timer-is-countdown nil)
356 (org-timer-set-mode-line 'off))
ed21c5c8 357 (message "Last timer canceled"))
c8d0cf5c
CD
358
359(defun org-timer-show-remaining-time ()
360 "Display the remaining time before the timer ends."
361 (interactive)
362 (require 'time)
ed21c5c8 363 (if (not org-timer-current-timer)
c8d0cf5c
CD
364 (message "No timer set")
365 (let* ((rtime (decode-time
ed21c5c8 366 (time-subtract (timer--time org-timer-current-timer)
c8d0cf5c
CD
367 (current-time))))
368 (rsecs (nth 0 rtime))
369 (rmins (nth 1 rtime)))
ed21c5c8 370 (message "%d minute(s) %d seconds left before next time out"
c8d0cf5c
CD
371 rmins rsecs))))
372
373;;;###autoload
86fbb8ca
CD
374(defun org-timer-set-timer (&optional opt)
375 "Prompt for a duration and set a timer.
376
377If `org-timer-default-timer' is not zero, suggest this value as
378the default duration for the timer. If a timer is already set,
afe98dfa 379prompt the user if she wants to replace it.
86fbb8ca
CD
380
381Called with a numeric prefix argument, use this numeric value as
382the duration of the timer.
383
384Called with a `C-u' prefix arguments, use `org-timer-default-timer'
385without prompting the user for a duration.
386
387With two `C-u' prefix arguments, use `org-timer-default-timer'
388without prompting the user for a duration and automatically
389replace any running timer."
390 (interactive "P")
391 (let ((minutes (or (and (numberp opt) (number-to-string opt))
392 (and (listp opt) (not (null opt))
393 (number-to-string org-timer-default-timer))
394 (read-from-minibuffer
395 "How many minutes left? "
396 (if (not (eq org-timer-default-timer 0))
397 (number-to-string org-timer-default-timer))))))
398 (if (not (string-match "[0-9]+" minutes))
399 (org-timer-show-remaining-time)
8223b1d2
BG
400 (let* ((mins (string-to-number (match-string 0 minutes)))
401 (secs (* mins 60))
402 (hl (cond
403 ((string-match "Org Agenda" (buffer-name))
404 (let* ((marker (or (get-text-property (point) 'org-marker)
405 (org-agenda-error)))
406 (hdmarker (or (get-text-property (point) 'org-hd-marker)
407 marker))
408 (pos (marker-position marker)))
409 (with-current-buffer (marker-buffer marker)
410 (widen)
411 (goto-char pos)
412 (org-show-entry)
413 (or (ignore-errors (org-get-heading))
414 (concat "File:" (file-name-nondirectory (buffer-file-name)))))))
415 ((derived-mode-p 'org-mode)
416 (or (ignore-errors (org-get-heading))
417 (concat "File:" (file-name-nondirectory (buffer-file-name)))))
418 (t (error "Not in an Org buffer"))))
419 timer-set)
420 (if (or (and org-timer-current-timer
421 (or (equal opt '(16))
422 (y-or-n-p "Replace current timer? ")))
423 (not org-timer-current-timer))
424 (progn
425 (require 'org-clock)
426 (when org-timer-current-timer
427 (cancel-timer org-timer-current-timer))
428 (setq org-timer-current-timer
429 (run-with-timer
430 secs nil `(lambda ()
431 (setq org-timer-current-timer nil)
432 (org-notify ,(format "%s: time out" hl) t)
433 (setq org-timer-timer-is-countdown nil)
434 (org-timer-set-mode-line 'off)
435 (run-hooks 'org-timer-done-hook))))
436 (run-hooks 'org-timer-set-hook)
437 (setq org-timer-timer-is-countdown t
438 org-timer-start-time
439 (time-add (current-time) (seconds-to-time (* mins 60))))
440 (org-timer-set-mode-line 'on))
441 (message "No timer set"))))))
c8d0cf5c 442
a2a2e7fb
CD
443(provide 'org-timer)
444
bdebdb64
BG
445;; Local variables:
446;; generated-autoload-file: "org-loaddefs.el"
447;; End:
448
bc23baaa 449;;; org-timer.el ends here