* etc/publicsuffix.txt: Update from source.
[bpt/emacs.git] / lisp / org / org-timer.el
CommitLineData
0bd48b37 1;;; org-timer.el --- The relative timer code for Org-mode
bc23baaa 2
ba318903 3;; Copyright (C) 2008-2014 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
30cb51f1 159(defvar org-timer-current-timer nil)
0bd48b37
CD
160(defun org-timer-stop ()
161 "Stop the relative timer."
162 (interactive)
ed21c5c8 163 (run-hooks 'org-timer-stop-hook)
0bd48b37 164 (setq org-timer-start-time nil
30cb51f1
BG
165 org-timer-pause-time nil
166 org-timer-current-timer nil)
63aa0982
BG
167 (org-timer-set-mode-line 'off)
168 (message "Timer stopped"))
0bd48b37 169
bc23baaa 170;;;###autoload
afe98dfa 171(defun org-timer (&optional restart no-insert-p)
bc23baaa
CD
172 "Insert a H:MM:SS string from the timer into the buffer.
173The first time this command is used, the timer is started. When used with
86fbb8ca 174a \\[universal-argument] prefix, force restarting the timer.
afe98dfa 175When used with a double prefix argument \\[universal-argument], change all the timer string
bc23baaa 176in the region by a fixed amount. This can be used to recalibrate a timer
afe98dfa
CD
177that was not started at the correct moment.
178
179If NO-INSERT-P is non-nil, return the string instead of inserting
180it in the buffer."
bc23baaa 181 (interactive "P")
afe98dfa
CD
182 (when (or (equal restart '(4)) (not org-timer-start-time))
183 (org-timer-start))
184 (if no-insert-p
185 (org-timer-value-string)
186 (insert (org-timer-value-string))))
0bd48b37
CD
187
188(defun org-timer-value-string ()
189 (format org-timer-format (org-timer-secs-to-hms (floor (org-timer-seconds)))))
190
afe98dfa 191(defvar org-timer-timer-is-countdown nil)
0bd48b37 192(defun org-timer-seconds ()
afe98dfa
CD
193 (if org-timer-timer-is-countdown
194 (- (org-float-time org-timer-start-time)
195 (org-float-time (current-time)))
196 (- (org-float-time (or org-timer-pause-time (current-time)))
197 (org-float-time org-timer-start-time))))
bc23baaa
CD
198
199;;;###autoload
200(defun org-timer-change-times-in-region (beg end delta)
201 "Change all h:mm:ss time in region by a DELTA."
202 (interactive
8223b1d2 203 "r\nsEnter time difference like \"-1:08:26\". Default is first time to zero: ")
bc23baaa
CD
204 (let ((re "[-+]?[0-9]+:[0-9]\\{2\\}:[0-9]\\{2\\}") p)
205 (unless (string-match "\\S-" delta)
206 (save-excursion
207 (goto-char beg)
208 (when (re-search-forward re end t)
209 (setq delta (match-string 0))
210 (if (equal (string-to-char delta) ?-)
211 (setq delta (substring delta 1))
212 (setq delta (concat "-" delta))))))
213 (setq delta (org-timer-hms-to-secs (org-timer-fix-incomplete delta)))
214 (when (= delta 0) (error "No change"))
215 (save-excursion
216 (goto-char end)
217 (while (re-search-backward re beg t)
218 (setq p (point))
219 (replace-match
220 (save-match-data
221 (org-timer-secs-to-hms (+ (org-timer-hms-to-secs (match-string 0)) delta)))
222 t t)
223 (goto-char p)))))
224
225;;;###autoload
226(defun org-timer-item (&optional arg)
33306645 227 "Insert a description-type item with the current timer value."
bc23baaa 228 (interactive "P")
3ab2c837
BG
229 (let ((itemp (org-in-item-p)) (pos (point)))
230 (cond
231 ;; In a timer list, insert with `org-list-insert-item',
232 ;; then fix the list.
233 ((and itemp (goto-char itemp) (org-at-item-timer-p))
234 (let* ((struct (org-list-struct))
235 (prevs (org-list-prevs-alist struct))
236 (s (concat (org-timer (when arg '(4)) t) ":: ")))
237 (setq struct (org-list-insert-item pos struct prevs nil s))
238 (org-list-write-struct struct (org-list-parents-alist struct))
239 (looking-at org-list-full-item-re)
240 (goto-char (match-end 0))))
241 ;; In a list of another type, don't break anything: throw an error.
242 (itemp (goto-char pos) (error "This is not a timer list"))
243 ;; Else, start a new list.
244 (t
245 (beginning-of-line)
8223b1d2 246 (org-indent-line)
3ab2c837
BG
247 (insert "- ")
248 (org-timer (when arg '(4)))
249 (insert ":: ")))))
bc23baaa
CD
250
251(defun org-timer-fix-incomplete (hms)
252 "If hms is a H:MM:SS string with missing hour or hour and minute, fix it."
253 (if (string-match "\\(?:\\([0-9]+:\\)?\\([0-9]+:\\)\\)?\\([0-9]+\\)" hms)
254 (replace-match
255 (format "%d:%02d:%02d"
256 (if (match-end 1) (string-to-number (match-string 1 hms)) 0)
257 (if (match-end 2) (string-to-number (match-string 2 hms)) 0)
258 (string-to-number (match-string 3 hms)))
259 t t hms)
33306645 260 (error "Cannot parse HMS string \"%s\"" hms)))
bc23baaa
CD
261
262(defun org-timer-hms-to-secs (hms)
263 "Convert h:mm:ss string to an integer time.
264If the string starts with a minus sign, the integer will be negative."
265 (if (not (string-match
266 "\\([-+]?[0-9]+\\):\\([0-9]\\{2\\}\\):\\([0-9]\\{2\\}\\)"
267 hms))
268 0
269 (let* ((h (string-to-number (match-string 1 hms)))
270 (m (string-to-number (match-string 2 hms)))
271 (s (string-to-number (match-string 3 hms)))
272 (sign (equal (substring (match-string 1 hms) 0 1) "-")))
273 (setq h (abs h))
274 (* (if sign -1 1) (+ s (* 60 (+ m (* 60 h))))))))
275
276(defun org-timer-secs-to-hms (s)
277 "Convert integer S into h:mm:ss.
33306645 278If the integer is negative, the string will start with \"-\"."
bc23baaa
CD
279 (let (sign m h)
280 (setq sign (if (< s 0) "-" "")
281 s (abs s)
282 m (/ s 60) s (- s (* 60 m))
283 h (/ m 60) m (- m (* 60 h)))
284 (format "%s%d:%02d:%02d" sign h m s)))
285
0bd48b37
CD
286(defvar org-timer-mode-line-timer nil)
287(defvar org-timer-mode-line-string nil)
288
289(defun org-timer-set-mode-line (value)
8bfe682a 290 "Set the mode-line display of the relative timer.
0bd48b37 291VALUE can be `on', `off', or `pause'."
8223b1d2
BG
292 (when (or (eq org-timer-display 'mode-line)
293 (eq org-timer-display 'both))
294 (or global-mode-string (setq global-mode-string '("")))
295 (or (memq 'org-timer-mode-line-string global-mode-string)
296 (setq global-mode-string
297 (append global-mode-string '(org-timer-mode-line-string)))))
298 (when (or (eq org-timer-display 'frame-title)
299 (eq org-timer-display 'both))
300 (or (memq 'org-timer-mode-line-string frame-title-format)
301 (setq frame-title-format
302 (append frame-title-format '(org-timer-mode-line-string)))))
0bd48b37
CD
303 (cond
304 ((equal value 'off)
305 (when org-timer-mode-line-timer
306 (cancel-timer org-timer-mode-line-timer)
307 (setq org-timer-mode-line-timer nil))
8223b1d2
BG
308 (when (or (eq org-timer-display 'mode-line)
309 (eq org-timer-display 'both))
310 (setq global-mode-string
311 (delq 'org-timer-mode-line-string global-mode-string)))
312 (when (or (eq org-timer-display 'frame-title)
313 (eq org-timer-display 'both))
314 (setq frame-title-format
315 (delq 'org-timer-mode-line-string frame-title-format)))
0bd48b37
CD
316 (force-mode-line-update))
317 ((equal value 'pause)
318 (when org-timer-mode-line-timer
319 (cancel-timer org-timer-mode-line-timer)
320 (setq org-timer-mode-line-timer nil)))
321 ((equal value 'on)
8223b1d2
BG
322 (when (or (eq org-timer-display 'mode-line)
323 (eq org-timer-display 'both))
324 (or global-mode-string (setq global-mode-string '("")))
325 (or (memq 'org-timer-mode-line-string global-mode-string)
326 (setq global-mode-string
327 (append global-mode-string '(org-timer-mode-line-string)))))
328 (when (or (eq org-timer-display 'frame-title)
329 (eq org-timer-display 'both))
330 (or (memq 'org-timer-mode-line-string frame-title-format)
331 (setq frame-title-format
332 (append frame-title-format '(org-timer-mode-line-string)))))
0bd48b37
CD
333 (org-timer-update-mode-line)
334 (when org-timer-mode-line-timer
8223b1d2
BG
335 (cancel-timer org-timer-mode-line-timer)
336 (setq org-timer-mode-line-timer nil))
337 (when org-timer-display
338 (setq org-timer-mode-line-timer
339 (run-with-timer 1 1 'org-timer-update-mode-line))))))
0bd48b37
CD
340
341(defun org-timer-update-mode-line ()
342 "Update the timer time in the mode line."
343 (if org-timer-pause-time
344 nil
345 (setq org-timer-mode-line-string
346 (concat " <" (substring (org-timer-value-string) 0 -1) ">"))
347 (force-mode-line-update)))
348
ed21c5c8
CD
349(defun org-timer-cancel-timer ()
350 "Cancel the current timer."
c8d0cf5c 351 (interactive)
ed21c5c8
CD
352 (when (eval org-timer-current-timer)
353 (run-hooks 'org-timer-cancel-hook)
354 (cancel-timer org-timer-current-timer)
afe98dfa
CD
355 (setq org-timer-current-timer nil)
356 (setq org-timer-timer-is-countdown nil)
357 (org-timer-set-mode-line 'off))
ed21c5c8 358 (message "Last timer canceled"))
c8d0cf5c
CD
359
360(defun org-timer-show-remaining-time ()
361 "Display the remaining time before the timer ends."
362 (interactive)
363 (require 'time)
ed21c5c8 364 (if (not org-timer-current-timer)
c8d0cf5c
CD
365 (message "No timer set")
366 (let* ((rtime (decode-time
ed21c5c8 367 (time-subtract (timer--time org-timer-current-timer)
c8d0cf5c
CD
368 (current-time))))
369 (rsecs (nth 0 rtime))
370 (rmins (nth 1 rtime)))
ed21c5c8 371 (message "%d minute(s) %d seconds left before next time out"
c8d0cf5c
CD
372 rmins rsecs))))
373
271672fa
BG
374(defvar org-clock-sound)
375
c8d0cf5c 376;;;###autoload
86fbb8ca
CD
377(defun org-timer-set-timer (&optional opt)
378 "Prompt for a duration and set a timer.
379
380If `org-timer-default-timer' is not zero, suggest this value as
381the default duration for the timer. If a timer is already set,
afe98dfa 382prompt the user if she wants to replace it.
86fbb8ca
CD
383
384Called with a numeric prefix argument, use this numeric value as
385the duration of the timer.
386
387Called with a `C-u' prefix arguments, use `org-timer-default-timer'
388without prompting the user for a duration.
389
390With two `C-u' prefix arguments, use `org-timer-default-timer'
391without prompting the user for a duration and automatically
392replace any running timer."
393 (interactive "P")
394 (let ((minutes (or (and (numberp opt) (number-to-string opt))
395 (and (listp opt) (not (null opt))
396 (number-to-string org-timer-default-timer))
397 (read-from-minibuffer
398 "How many minutes left? "
399 (if (not (eq org-timer-default-timer 0))
400 (number-to-string org-timer-default-timer))))))
401 (if (not (string-match "[0-9]+" minutes))
402 (org-timer-show-remaining-time)
8223b1d2
BG
403 (let* ((mins (string-to-number (match-string 0 minutes)))
404 (secs (* mins 60))
405 (hl (cond
406 ((string-match "Org Agenda" (buffer-name))
407 (let* ((marker (or (get-text-property (point) 'org-marker)
408 (org-agenda-error)))
409 (hdmarker (or (get-text-property (point) 'org-hd-marker)
410 marker))
411 (pos (marker-position marker)))
412 (with-current-buffer (marker-buffer marker)
413 (widen)
414 (goto-char pos)
415 (org-show-entry)
416 (or (ignore-errors (org-get-heading))
417 (concat "File:" (file-name-nondirectory (buffer-file-name)))))))
418 ((derived-mode-p 'org-mode)
419 (or (ignore-errors (org-get-heading))
420 (concat "File:" (file-name-nondirectory (buffer-file-name)))))
421 (t (error "Not in an Org buffer"))))
422 timer-set)
423 (if (or (and org-timer-current-timer
424 (or (equal opt '(16))
425 (y-or-n-p "Replace current timer? ")))
426 (not org-timer-current-timer))
427 (progn
428 (require 'org-clock)
429 (when org-timer-current-timer
430 (cancel-timer org-timer-current-timer))
431 (setq org-timer-current-timer
432 (run-with-timer
433 secs nil `(lambda ()
434 (setq org-timer-current-timer nil)
271672fa 435 (org-notify ,(format "%s: time out" hl) ,org-clock-sound)
8223b1d2
BG
436 (setq org-timer-timer-is-countdown nil)
437 (org-timer-set-mode-line 'off)
438 (run-hooks 'org-timer-done-hook))))
439 (run-hooks 'org-timer-set-hook)
440 (setq org-timer-timer-is-countdown t
441 org-timer-start-time
442 (time-add (current-time) (seconds-to-time (* mins 60))))
443 (org-timer-set-mode-line 'on))
444 (message "No timer set"))))))
c8d0cf5c 445
a2a2e7fb
CD
446(provide 'org-timer)
447
bdebdb64
BG
448;; Local variables:
449;; generated-autoload-file: "org-loaddefs.el"
450;; End:
451
bc23baaa 452;;; org-timer.el ends here