Commit | Line | Data |
---|---|---|
0bd48b37 | 1 | ;;; org-timer.el --- The relative timer code for Org-mode |
bc23baaa | 2 | |
114f9c96 | 3 | ;; Copyright (C) 2008, 2009, 2010 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 | |
5dec9555 | 8 | ;; Version: 6.33x |
bc23baaa CD |
9 | ;; |
10 | ;; This file is part of GNU Emacs. | |
11 | ;; | |
12 | ;; GNU Emacs is free software: you can redistribute it and/or modify | |
13 | ;; it under the terms of the GNU General Public License as published by | |
14 | ;; the Free Software Foundation, either version 3 of the License, or | |
15 | ;; (at your option) any later version. | |
16 | ||
17 | ;; GNU Emacs is distributed in the hope that it will be useful, | |
18 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | |
19 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
20 | ;; GNU General Public License for more details. | |
21 | ||
22 | ;; You should have received a copy of the GNU General Public License | |
23 | ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. | |
24 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
25 | ;; | |
26 | ;;; Commentary: | |
27 | ||
28 | ;; This file contains the relative timer code for Org-mode | |
29 | ||
30 | (require 'org) | |
31 | ||
c8d0cf5c CD |
32 | (declare-function org-show-notification "org-clock" (parameters)) |
33 | (declare-function org-agenda-error "org-agenda" ()) | |
34 | ||
bc23baaa CD |
35 | (defvar org-timer-start-time nil |
36 | "t=0 for the running timer.") | |
37 | ||
0bd48b37 CD |
38 | (defvar org-timer-pause-time nil |
39 | "Time when the timer was paused.") | |
40 | ||
bc23baaa CD |
41 | (defconst org-timer-re "\\([-+]?[0-9]+\\):\\([0-9]\\{2\\}\\):\\([0-9]\\{2\\}\\)" |
42 | "Regular expression used to match timer stamps.") | |
43 | ||
44 | (defcustom org-timer-format "%s " | |
45 | "The format to insert the time of the timer. | |
46 | This format must contain one instance of \"%s\" which will be replaced by | |
47 | the value of the relative timer." | |
48 | :group 'org-time | |
49 | :type 'string) | |
50 | ||
51 | ;;;###autoload | |
52 | (defun org-timer-start (&optional offset) | |
53 | "Set the starting time for the relative timer to now. | |
54 | When called with prefix argument OFFSET, prompt the user for an offset time, | |
55 | with the default taken from a timer stamp at point, if any. | |
56 | If OFFSET is a string or an integer, it is directly taken to be the offset | |
57 | without user interaction. | |
58 | When called with a double prefix arg, all timer strings in the active | |
59 | region will be shifted by a specific amount. You will be prompted for | |
60 | the amount, with the default to make the first timer string in | |
61 | the region 0:00:00." | |
62 | (interactive "P") | |
63 | (if (equal offset '(16)) | |
64 | (call-interactively 'org-timer-change-times-in-region) | |
65 | (let (delta def s) | |
66 | (if (not offset) | |
67 | (setq org-timer-start-time (current-time)) | |
68 | (cond | |
69 | ((integerp offset) (setq delta offset)) | |
70 | ((stringp offset) (setq delta (org-timer-hms-to-secs offset))) | |
71 | (t | |
72 | (setq def (if (org-in-regexp org-timer-re) | |
73 | (match-string 0) | |
74 | "0:00:00") | |
75 | s (read-string | |
76 | (format "Restart timer with offset [%s]: " def))) | |
77 | (unless (string-match "\\S-" s) (setq s def)) | |
78 | (setq delta (org-timer-hms-to-secs (org-timer-fix-incomplete s))))) | |
79 | (setq org-timer-start-time | |
80 | (seconds-to-time | |
54a0dee5 | 81 | (- (org-float-time) (org-timer-hms-to-secs s))))) |
0bd48b37 | 82 | (org-timer-set-mode-line 'on) |
bc23baaa CD |
83 | (message "Timer start time set to %s, current value is %s" |
84 | (format-time-string "%T" org-timer-start-time) | |
85 | (org-timer-secs-to-hms (or delta 0)))))) | |
86 | ||
0bd48b37 CD |
87 | (defun org-timer-pause-or-continue (&optional stop) |
88 | "Pause or continue the relative timer. With prefix arg, stop it entirely." | |
89 | (interactive "P") | |
90 | (cond | |
91 | (stop (org-timer-stop)) | |
92 | ((not org-timer-start-time) (error "No timer is running")) | |
93 | (org-timer-pause-time | |
94 | ;; timer is paused, continue | |
95 | (setq org-timer-start-time | |
96 | (seconds-to-time | |
97 | (- | |
54a0dee5 CD |
98 | (org-float-time) |
99 | (- (org-float-time org-timer-pause-time) | |
100 | (org-float-time org-timer-start-time)))) | |
0bd48b37 CD |
101 | org-timer-pause-time nil) |
102 | (org-timer-set-mode-line 'on) | |
103 | (message "Timer continues at %s" (org-timer-value-string))) | |
104 | (t | |
105 | ;; pause timer | |
106 | (setq org-timer-pause-time (current-time)) | |
107 | (org-timer-set-mode-line 'pause) | |
108 | (message "Timer paused at %s" (org-timer-value-string))))) | |
109 | ||
110 | (defun org-timer-stop () | |
111 | "Stop the relative timer." | |
112 | (interactive) | |
113 | (setq org-timer-start-time nil | |
114 | org-timer-pause-time nil) | |
115 | (org-timer-set-mode-line 'off)) | |
116 | ||
bc23baaa CD |
117 | ;;;###autoload |
118 | (defun org-timer (&optional restart) | |
119 | "Insert a H:MM:SS string from the timer into the buffer. | |
120 | The first time this command is used, the timer is started. When used with | |
121 | a `C-u' prefix, force restarting the timer. | |
122 | When used with a double prefix arg `C-u C-u', change all the timer string | |
123 | in the region by a fixed amount. This can be used to recalibrate a timer | |
124 | that was not started at the correct moment." | |
125 | (interactive "P") | |
126 | (if (equal restart '(4)) (org-timer-start)) | |
127 | (or org-timer-start-time (org-timer-start)) | |
0bd48b37 CD |
128 | (insert (org-timer-value-string))) |
129 | ||
130 | (defun org-timer-value-string () | |
131 | (format org-timer-format (org-timer-secs-to-hms (floor (org-timer-seconds))))) | |
132 | ||
133 | (defun org-timer-seconds () | |
54a0dee5 CD |
134 | (- (org-float-time (or org-timer-pause-time (current-time))) |
135 | (org-float-time org-timer-start-time))) | |
bc23baaa CD |
136 | |
137 | ;;;###autoload | |
138 | (defun org-timer-change-times-in-region (beg end delta) | |
139 | "Change all h:mm:ss time in region by a DELTA." | |
140 | (interactive | |
141 | "r\nsEnter time difference like \"-1:08:26\". Default is first time to zero: ") | |
142 | (let ((re "[-+]?[0-9]+:[0-9]\\{2\\}:[0-9]\\{2\\}") p) | |
143 | (unless (string-match "\\S-" delta) | |
144 | (save-excursion | |
145 | (goto-char beg) | |
146 | (when (re-search-forward re end t) | |
147 | (setq delta (match-string 0)) | |
148 | (if (equal (string-to-char delta) ?-) | |
149 | (setq delta (substring delta 1)) | |
150 | (setq delta (concat "-" delta)))))) | |
151 | (setq delta (org-timer-hms-to-secs (org-timer-fix-incomplete delta))) | |
152 | (when (= delta 0) (error "No change")) | |
153 | (save-excursion | |
154 | (goto-char end) | |
155 | (while (re-search-backward re beg t) | |
156 | (setq p (point)) | |
157 | (replace-match | |
158 | (save-match-data | |
159 | (org-timer-secs-to-hms (+ (org-timer-hms-to-secs (match-string 0)) delta))) | |
160 | t t) | |
161 | (goto-char p))))) | |
162 | ||
163 | ;;;###autoload | |
164 | (defun org-timer-item (&optional arg) | |
33306645 | 165 | "Insert a description-type item with the current timer value." |
bc23baaa CD |
166 | (interactive "P") |
167 | (let ((ind 0)) | |
168 | (save-excursion | |
169 | (skip-chars-backward " \n\t") | |
170 | (condition-case nil | |
171 | (progn | |
172 | (org-beginning-of-item) | |
173 | (setq ind (org-get-indentation))) | |
174 | (error nil))) | |
175 | (or (bolp) (newline)) | |
176 | (org-indent-line-to ind) | |
177 | (insert "- ") | |
178 | (org-timer (if arg '(4))) | |
179 | (insert ":: "))) | |
180 | ||
181 | (defun org-timer-fix-incomplete (hms) | |
182 | "If hms is a H:MM:SS string with missing hour or hour and minute, fix it." | |
183 | (if (string-match "\\(?:\\([0-9]+:\\)?\\([0-9]+:\\)\\)?\\([0-9]+\\)" hms) | |
184 | (replace-match | |
185 | (format "%d:%02d:%02d" | |
186 | (if (match-end 1) (string-to-number (match-string 1 hms)) 0) | |
187 | (if (match-end 2) (string-to-number (match-string 2 hms)) 0) | |
188 | (string-to-number (match-string 3 hms))) | |
189 | t t hms) | |
33306645 | 190 | (error "Cannot parse HMS string \"%s\"" hms))) |
bc23baaa CD |
191 | |
192 | (defun org-timer-hms-to-secs (hms) | |
193 | "Convert h:mm:ss string to an integer time. | |
194 | If the string starts with a minus sign, the integer will be negative." | |
195 | (if (not (string-match | |
196 | "\\([-+]?[0-9]+\\):\\([0-9]\\{2\\}\\):\\([0-9]\\{2\\}\\)" | |
197 | hms)) | |
198 | 0 | |
199 | (let* ((h (string-to-number (match-string 1 hms))) | |
200 | (m (string-to-number (match-string 2 hms))) | |
201 | (s (string-to-number (match-string 3 hms))) | |
202 | (sign (equal (substring (match-string 1 hms) 0 1) "-"))) | |
203 | (setq h (abs h)) | |
204 | (* (if sign -1 1) (+ s (* 60 (+ m (* 60 h)))))))) | |
205 | ||
206 | (defun org-timer-secs-to-hms (s) | |
207 | "Convert integer S into h:mm:ss. | |
33306645 | 208 | If the integer is negative, the string will start with \"-\"." |
bc23baaa CD |
209 | (let (sign m h) |
210 | (setq sign (if (< s 0) "-" "") | |
211 | s (abs s) | |
212 | m (/ s 60) s (- s (* 60 m)) | |
213 | h (/ m 60) m (- m (* 60 h))) | |
214 | (format "%s%d:%02d:%02d" sign h m s))) | |
215 | ||
0bd48b37 CD |
216 | (defvar org-timer-mode-line-timer nil) |
217 | (defvar org-timer-mode-line-string nil) | |
218 | ||
219 | (defun org-timer-set-mode-line (value) | |
8bfe682a | 220 | "Set the mode-line display of the relative timer. |
0bd48b37 CD |
221 | VALUE can be `on', `off', or `pause'." |
222 | (or global-mode-string (setq global-mode-string '(""))) | |
223 | (or (memq 'org-timer-mode-line-string global-mode-string) | |
224 | (setq global-mode-string | |
225 | (append global-mode-string '(org-timer-mode-line-string)))) | |
226 | (cond | |
227 | ((equal value 'off) | |
228 | (when org-timer-mode-line-timer | |
229 | (cancel-timer org-timer-mode-line-timer) | |
230 | (setq org-timer-mode-line-timer nil)) | |
231 | (setq global-mode-string | |
232 | (delq 'org-timer-mode-line-string global-mode-string)) | |
233 | (force-mode-line-update)) | |
234 | ((equal value 'pause) | |
235 | (when org-timer-mode-line-timer | |
236 | (cancel-timer org-timer-mode-line-timer) | |
237 | (setq org-timer-mode-line-timer nil))) | |
238 | ((equal value 'on) | |
239 | (or global-mode-string (setq global-mode-string '(""))) | |
240 | (or (memq 'org-timer-mode-line-string global-mode-string) | |
241 | (setq global-mode-string | |
242 | (append global-mode-string '(org-timer-mode-line-string)))) | |
243 | (org-timer-update-mode-line) | |
244 | (when org-timer-mode-line-timer | |
245 | (cancel-timer org-timer-mode-line-timer)) | |
246 | (setq org-timer-mode-line-timer | |
247 | (run-with-timer 1 1 'org-timer-update-mode-line))))) | |
248 | ||
249 | (defun org-timer-update-mode-line () | |
250 | "Update the timer time in the mode line." | |
251 | (if org-timer-pause-time | |
252 | nil | |
253 | (setq org-timer-mode-line-string | |
254 | (concat " <" (substring (org-timer-value-string) 0 -1) ">")) | |
255 | (force-mode-line-update))) | |
256 | ||
c8d0cf5c CD |
257 | (defvar org-timer-timer1 nil) |
258 | (defvar org-timer-timer2 nil) | |
259 | (defvar org-timer-timer3 nil) | |
260 | (defvar org-timer-last-timer nil) | |
261 | ||
262 | (defun org-timer-cancel-timers () | |
263 | "Reset all timers." | |
264 | (interactive) | |
265 | (mapc (lambda(timer) | |
266 | (when (eval timer) | |
267 | (cancel-timer timer) | |
268 | (setq timer nil))) | |
269 | '(org-timer-timer1 | |
270 | org-timer-timer2 | |
271 | org-timer-timer3)) | |
272 | (message "All timers reset")) | |
273 | ||
274 | (defun org-timer-show-remaining-time () | |
275 | "Display the remaining time before the timer ends." | |
276 | (interactive) | |
277 | (require 'time) | |
278 | (if (and (not org-timer-timer1) | |
279 | (not org-timer-timer2) | |
280 | (not org-timer-timer3)) | |
281 | (message "No timer set") | |
282 | (let* ((rtime (decode-time | |
283 | (time-subtract (timer--time org-timer-last-timer) | |
284 | (current-time)))) | |
285 | (rsecs (nth 0 rtime)) | |
286 | (rmins (nth 1 rtime))) | |
8bfe682a | 287 | (message "%d minutes %d seconds left before next time out" |
c8d0cf5c CD |
288 | rmins rsecs)))) |
289 | ||
290 | ;;;###autoload | |
291 | (defun org-timer-set-timer (minutes) | |
292 | "Set a timer." | |
293 | (interactive "sTime out in (min)? ") | |
294 | (if (not (string-match "[0-9]+" minutes)) | |
295 | (org-timer-show-remaining-time) | |
296 | (let* ((mins (string-to-number (match-string 0 minutes))) | |
297 | (secs (* mins 60)) | |
298 | (hl (cond | |
299 | ((string-match "Org Agenda" (buffer-name)) | |
300 | (let* ((marker (or (get-text-property (point) 'org-marker) | |
301 | (org-agenda-error))) | |
302 | (hdmarker (or (get-text-property (point) 'org-hd-marker) | |
303 | marker)) | |
304 | (pos (marker-position marker))) | |
305 | (with-current-buffer (marker-buffer marker) | |
306 | (widen) | |
307 | (goto-char pos) | |
308 | (org-show-entry) | |
309 | (org-get-heading)))) | |
310 | ((eq major-mode 'org-mode) | |
311 | (org-get-heading)) | |
312 | (t (error "Not in an Org buffer")))) | |
313 | timer-set) | |
314 | (mapcar (lambda(timer) | |
8bfe682a CD |
315 | (when (not (or (eval timer) timer-set)) |
316 | (setq timer-set t) | |
317 | (setq org-timer-last-timer | |
318 | (run-with-timer | |
319 | secs nil 'org-notify (format "%s: time out" hl) t)) | |
320 | (set timer org-timer-last-timer))) | |
c8d0cf5c CD |
321 | '(org-timer-timer1 |
322 | org-timer-timer2 | |
323 | org-timer-timer3))))) | |
324 | ||
a2a2e7fb CD |
325 | (provide 'org-timer) |
326 | ||
bc23baaa CD |
327 | ;; arch-tag: 97538f8c-3871-4509-8f23-1e7b3ff3d107 |
328 | ||
329 | ;;; org-timer.el ends here |