* Patch for resolving "away time" when clocked in
@ 2009-10-16 7:03 John Wiegley
2009-10-16 14:25 ` Jeff Kowalczyk
` (3 more replies)
0 siblings, 4 replies; 16+ messages in thread
From: John Wiegley @ 2009-10-16 7:03 UTC (permalink / raw)
To: Org-mode Mode
[-- Attachment #1: Type: text/plain, Size: 3533 bytes --]
Looking for anyone who uses Org's time clocking facilities and is
willing to test this with me. I've been using it for a couple of days
now. The functionality is based on the way the commercial app
OfficeTime handles idleness.
Excerpt from the new manual section:
# Resolving idle time
If you clock in on a work item, and then walk away from your
computer---perhaps to take a phone call---you often need to
``resolve'' the
time you were away by either subtracting it from the current clock, or
applying it to another one.
By customizing the variable @code{org-clock-idle-time} to some
integer, such
as 10 or 15, Emacs can alert you when you get back to your computer
after
being idle for that many minutes@footnote{On computers using Mac OS X,
idleness is based on actual user idleness, not just Emacs' idle
time.}, and
ask what you want to do with the idle time. There will be a question
waiting
for you when you get back, indicating how much idle time has passed
(constantly updated with the current amount), as well as a set of
choices to
correct the discrepancy:
@table @kbd
@item k
To keep some or all of the minutes and stay clocked in, press @key
{k}. Org
will ask how many of the minutes to keep. Press @key{RET} to keep
them all,
effectively changing nothing, or enter a number to keep that many
minutes.
@item K
If you use the shift key and press @key{K}, it will keep however many
minutes
you request and then immediately clock out of that task. If you keep
all of
the minutes, this is the same as just clocking out of the current task.
@item s
To keep none of the minutes, use @key{s} to subtract all the away time
from
the clock, and then check back in from the moment you returned.
@item S
To keep none of the minutes and just clock out at the start of the
away time,
use the shift key and press @key{S}. Remember that using shift will
always
leave you clocked out, no matter which option you choose.
@item C
To cancel the clock altogether, use @key{C}. Note that if instead of
cancelling you subtract the away time, and the resulting clock amount
is less
than a minute, the clock will still be cancelled rather than clutter
up the
log with an empty entry.
@end table
What if you subtracted those away minutes from the current clock, and
now
want to apply them to a new clock? Simply clock in to any task
immediately
after the subtraction. Org will notice that you have subtracted time
``on
the books'', so to speak, and will ask if you want to apply those
minutes to
the next task you clock in on.
There is one other instance when this clock resolution magic occurs.
Say you
were clocked in and hacking away, and suddenly your cat chased a mouse
who
scared a hamster that crashed into your UPS's power button! You
suddenly
lose all your buffers, but thanks to auto-save you still have your
recent Org
mode changes, including your last clock in.
If you restart Emacs and clock into any task, Org will notice that you
have a
dangling clock which was never clocked out from your last session.
Using the
Org file's last modified time as the beginning of the ``away'' period,
Org
will ask how you want to resolve that unaccounted-for time. The logic
and
behavior is identical to dealing with away time due to idleness, it's
just
happening due to a recovery event rather than a set amount of idle time.
You can also check all the files visited by your Org agenda for dangling
clocks at any time using @kbd{M-x org-resolve-clocks}.
John
[-- Attachment #2: 0001-Added-clock-resolution-logic.patch --]
[-- Type: application/octet-stream, Size: 23361 bytes --]
From 215cec3786be27b5752a55dfd21c6024ecda4f20 Mon Sep 17 00:00:00 2001
From: John Wiegley <johnw@newartisans.com>
Date: Tue, 13 Oct 2009 03:38:16 -0400
Subject: [PATCH] Added "clock resolution" logic
Allows the user to find and resolve dangling clock entries, as well as
notify them if they have been idle while clocked in.
There are several ways to resolve an open clock (whether dangling or
idle):
k - Keep X minutes of the time, default being all of the away time
(for dangling entries, the away time is computed from the last
time the org file was saved)
K - Keep X minutes, then immediately clock out
s - Subtract all of the idle/away minutes and resume clock from now
S - Subtract all of the idle/away minutes, and clock out
C - Cancel the clock
Note that some of these keys work without affecting the current clock,
unless the entry being operated on happens to be the current clock.
This means if you clocked into a different task, ran M-x
org-resolve-clocks and found a dangling entry, you could resolve it
using S without interrupting the current clock. If you resolved with k
or s, however, it would clock you out of the active clock, and back into
the one being resolved, since those keys assume a desire to resume the
task that was orphaned.
---
doc/org.texi | 69 +++++++++++-
lisp/org-clock.el | 334 ++++++++++++++++++++++++++++++++++++++++++++++++++++-
lisp/org.el | 4 +-
3 files changed, 399 insertions(+), 8 deletions(-)
diff --git a/doc/org.texi b/doc/org.texi
index 538aba8..e2aaa4e 100644
--- a/doc/org.texi
+++ b/doc/org.texi
@@ -231,6 +231,7 @@ Dates and Times
* Creating timestamps:: Commands which insert timestamps
* Deadlines and scheduling:: Planning your work
* Clocking work time:: Tracking how long you spend on a task
+* Resolving idle time::
* Effort estimates:: Planning work effort in advance
* Relative timer:: Notes with a running timer
@@ -4703,6 +4704,7 @@ is used in a much wider sense.
* Creating timestamps:: Commands which insert timestamps
* Deadlines and scheduling:: Planning your work
* Clocking work time:: Tracking how long you spend on a task
+* Resolving idle time::
* Effort estimates:: Planning work effort in advance
* Relative timer:: Notes with a running timer
@end menu
@@ -5220,7 +5222,7 @@ subtree, with dates shifted in each copy. The command @kbd{C-c C-x c} was
created for this purpose, it is described in @ref{Structure editing}.
-@node Clocking work time, Effort estimates, Deadlines and scheduling, Dates and Times
+@node Clocking work time, Resolving idle time, Deadlines and scheduling, Dates and Times
@section Clocking work time
Org mode allows you to clock the time you spend on specific tasks in a
@@ -5405,7 +5407,70 @@ The @kbd{l} key may be used in the timeline (@pxref{Timeline}) and in
the agenda (@pxref{Weekly/daily agenda}) to show which tasks have been
worked on or closed during a day.
-@node Effort estimates, Relative timer, Clocking work time, Dates and Times
+@node Resolving idle time, Effort estimates, Clocking work time, Dates and Times
+@section Resolving idle time
+@cindex resolve idle time
+
+@cindex idle, resolve, dangling
+If you clock in on a work item, and then walk away from your
+computer---perhaps to take a phone call---you often need to ``resolve'' the
+time you were away by either subtracting it from the current clock, or
+applying it to another one.
+
+@vindex org-clock-idle-time
+By customizing the variable @code{org-clock-idle-time} to some integer, such
+as 10 or 15, Emacs can alert you when you get back to your computer after
+being idle for that many minutes, and ask what you want to do with the idle
+time. There will be a question waiting for you when you get back, indicating
+how much idle time has passed (constantly updated with the current amount),
+as well as a set of choices to correct the discrepancy:
+
+@table @kbd
+@item k
+To keep some or all of the minutes and stay clocked in, press @key{k}. Org
+will ask how many of the minutes to keep. Press @key{RET} to keep them all,
+effectively changing nothing, or enter a number to keep that many minutes.
+@item K
+If you use the shift key and press @key{K}, it will keep however many minutes
+you request and then immediately clock out of that task. If you keep all of
+the minutes, this is the same as just clocking out of the current task.
+@item s
+To keep none of the minutes, use @key{s} to subtract all the away time from
+the clock, and then check back in from the moment you returned.
+@item S
+To keep none of the minutes and just clock out at the start of the away time,
+use the shift key and press @key{S}. Remember that using shift will always
+leave you clocked out, no matter which option you choose.
+@item C
+To cancel the clock altogether, use @key{C}. Note that if instead of
+cancelling you subtract the away time, and the resulting clock amount is less
+than a minute, the clock will still be cancelled rather than clutter up the
+log with an empty entry.
+@end table
+
+What if you subtracted those away minutes from the current clock, and now
+want to apply them to a new clock? Simply clock in to any task immediately
+after the subtraction. Org will notice that you have subtracted time ``on
+the books'', so to speak, and will ask if you want to apply those minutes to
+the next task you clock in on.
+
+There is one other instance when this clock resolution magic occurs. Say you
+were clocked in and hacking away, and suddenly your cat chased a mouse who
+scared a hamster that crashed into your UPS's power button! You suddenly
+lose all your buffers, but thanks to auto-save you still have your recent Org
+mode changes, including your last clock in.
+
+If you restart Emacs and clock into any task, Org will notice that you have a
+dangling clock which was never clocked out from your last session. Using the
+Org file's last modified time as the beginning of the ``away'' period, Org
+will ask how you want to resolve that unaccounted-for time. The logic and
+behavior is identical to dealing with away time due to idleness, it's just
+happening due to a recovery event rather than a set amount of idle time.
+
+You can also check all the files visited by your Org agenda for dangling
+clocks at any time using @kbd{M-x org-resolve-clocks}.
+
+@node Effort estimates, Relative timer, Resolving idle time, Dates and Times
@section Effort estimates
@cindex effort estimates
diff --git a/lisp/org-clock.el b/lisp/org-clock.el
index 97e5552..e00ecf0 100644
--- a/lisp/org-clock.el
+++ b/lisp/org-clock.el
@@ -228,10 +228,14 @@ to add an effort property.")
(put 'org-mode-line-string 'risky-local-variable t)
(defvar org-clock-mode-line-timer nil)
+(defvar org-clock-idle-timer nil)
(defvar org-clock-heading "")
(defvar org-clock-heading-for-remember "")
(defvar org-clock-start-time "")
+(defvar org-clock-left-over-time ""
+ "If non-nil, user cancelled a clock; this is when leftover time started.")
+
(defvar org-clock-effort ""
"Effort estimate of the currently clocking task")
@@ -495,6 +499,294 @@ Use alsa's aplay tool if available."
(defvar org-clock-mode-line-entry nil
"Information for the modeline about the running clock.")
+(defun org-find-open-clocks (file)
+ "Search through the given file and find all open clocks."
+ (let ((buf (or (get-file-buffer file)
+ (find-file-noselect file)))
+ clocks)
+ (with-current-buffer buf
+ (save-excursion
+ (goto-char (point-min))
+ (while (re-search-forward "CLOCK: \\(\\[.*?\\]\\)$" nil t)
+ (push (cons (copy-marker (1- (match-end 1)) t)
+ (org-time-string-to-time (match-string 1))) clocks))))
+ clocks))
+
+(defsubst org-is-active-clock (clock)
+ "Return t if CLOCK is the currently active clock."
+ (and (org-clock-is-active)
+ (= org-clock-marker (car clock))))
+
+(defmacro org-with-clock-position (clock &rest forms)
+ "Evaluate FORMS with CLOCK as the current active clock."
+ `(with-current-buffer (marker-buffer (car ,clock))
+ (save-excursion
+ (save-restriction
+ (widen)
+ (goto-char (car ,clock))
+ (beginning-of-line)
+ ,@forms))))
+
+(put 'org-with-clock-position 'lisp-indent-function 1)
+
+(defmacro org-with-clock (clock &rest forms)
+ "Evaluate FORMS with CLOCK as the current active clock.
+This macro also protects the current active clock from being altered."
+ `(org-with-clock-position ,clock
+ (let ((org-clock-start-time (cdr ,clock))
+ (org-clock-total-time)
+ (org-clock-history)
+ (org-clock-effort)
+ (org-clock-marker (car ,clock))
+ (org-clock-hd-marker (save-excursion
+ (outline-back-to-heading t)
+ (point-marker))))
+ ,@forms)))
+
+(put 'org-with-clock 'lisp-indent-function 1)
+
+(defsubst org-clock-clock-in (clock &optional resume)
+ "Clock in to the clock located by CLOCK.
+If necessary, clock-out of the currently active clock."
+ (org-with-clock-position clock
+ (let ((org-clock-in-resume (or resume org-clock-in-resume)))
+ (org-clock-in))))
+
+(defsubst org-clock-clock-out (clock &optional fail-quietly at-time)
+ "Clock out of the clock located by CLOCK."
+ (let ((temp (copy-marker (car clock)
+ (marker-insertion-type (car clock)))))
+ (if (org-is-active-clock clock)
+ (org-clock-out fail-quietly at-time)
+ (org-with-clock clock
+ (org-clock-out fail-quietly at-time)))
+ (setcar clock temp)))
+
+(defsubst org-clock-clock-cancel (clock)
+ "Cancel the clock located by CLOCK."
+ (let ((temp (copy-marker (car clock)
+ (marker-insertion-type (car clock)))))
+ (if (org-is-active-clock clock)
+ (org-clock-cancel)
+ (org-with-clock clock
+ (org-clock-cancel)))
+ (setcar clock temp)))
+
+(defun org-clock-resolve-clock (clock resolve-to last-valid &optional
+ close-p restart-p fail-quietly)
+ "Resolve `CLOCK' given the time `RESOLVE-TO', and the present.
+`CLOCK' is a cons cell of the form (MARKER START-TIME).
+This routine can do one of many things:
+
+ if `RESOLVE-TO' is nil
+ if `CLOSE-P' is non-nil, give an error
+ if this clock is the active clock, cancel it
+ else delete the clock line (as if it never happened)
+ if `RESTART-P' is non-nil, start a new clock
+
+ else if `RESOLVE-TO' is the symbol `now'
+ if `RESTART-P' is non-nil, give an error
+ if `CLOSE-P' is non-nil, clock out the entry and
+ if this clock is the active clock, stop it
+ else if this clock is the active clock, do nothing
+ else if there is no active clock, resume this clock
+ else ask to cancel the active clock, and if so,
+ resume this clock after cancelling it
+
+ else if `RESOLVE-TO' is some date in the future
+ give an error about `RESOLVE-TO' being invalid
+
+ else if `RESOLVE-TO' is some date in the past
+ if `RESTART-P' is non-nil, give an error
+ if `CLOSE-P' is non-nil, enter a closing time and
+ if this clock is the active clock, stop it
+ else if this clock is the active clock, enter a
+ closing time, stop the current clock, then
+ start a new clock for the same item
+ else just enter a closing time for this clock
+ and then start a new clock for the same item"
+ (cond
+ ((null resolve-to)
+ (org-clock-clock-cancel clock)
+ (if restart-p
+ (org-clock-clock-in clock)))
+
+ ((eq resolve-to 'now)
+ (if restart-p
+ (error "RESTART-P is not valid here"))
+ (if close-p
+ (org-clock-clock-out clock fail-quietly)
+ (unless (org-is-active-clock clock)
+ (org-clock-clock-in clock t))))
+
+ ((not (time-less-p resolve-to (current-time)))
+ (error "RESOLVE-TO must refer to a time in the past"))
+
+ (t
+ (if restart-p
+ (error "RESTART-P is not valid here"))
+ (if close-p
+ (progn
+ (org-clock-clock-out clock fail-quietly resolve-to)
+ (setq org-clock-left-over-time last-valid))
+ (org-clock-clock-out clock fail-quietly resolve-to)
+ (org-clock-clock-in clock)))))
+
+(defun org-clock-resolve (clock &optional prompt-fn last-valid fail-quietly)
+ "Resolve an open org-mode clock.
+An open clock was found, with `dangling' possibly being non-nil.
+If this function was invoked with a prefix argument, non-dangling
+open clocks are ignored. The given clock requires some sort of
+user intervention to resolve it, either because a clock was left
+dangling or due to an idle timeout. The clock resolution can
+either be:
+
+ (a) deleted, the user doesn't care about the clock
+ (b) restarted from the current time (if no other clock is open)
+ (c) closed, giving the clock X minutes
+ (d) closed and then restarted
+ (e) resumed, as if the user had never left
+
+The format of clock is (CONS MARKER START-TIME), where MARKER
+identifies the buffer and position the clock is open at (and
+thus, the heading it's under), and START-TIME is when the clock
+was started."
+ (assert clock)
+ (let* ((ch (progn
+ (unless org-clock-resolving-clocks-due-to-idleness
+ (org-with-clock clock
+ (org-clock-goto))
+ (with-current-buffer (marker-buffer (car clock))
+ (goto-char (car clock))))
+ (let (char-pressed)
+ (while (null char-pressed)
+ (setq char-pressed
+ (read-char (concat (funcall prompt-fn clock)
+ " [(kK)eep (sS)ubtract (C)ancel]? ")
+ nil 45)))
+ char-pressed)))
+ (last-modified (unless last-valid
+ (with-current-buffer (marker-buffer (car clock))
+ (nth 5 (file-attributes (buffer-file-name))))))
+ (default (floor (/ (time-to-seconds
+ (time-subtract (current-time)
+ (or last-valid
+ last-modified))) 60)))
+ (keep (and (memq ch '(?k ?K))
+ (read-number (format "Keep how many minutes (since %s)? "
+ (if last-valid
+ "idle" "org last saved"))
+ default)))
+ (subtractp (memq ch '(?s ?S)))
+ (barely-started-p (< (- (time-to-seconds (or last-valid last-modified))
+ (time-to-seconds (cdr clock))) 45))
+ (start-over (and subtractp barely-started-p)))
+ (if (or (null ch)
+ (not (memq ch '(?k ?K ?s ?S ?C))))
+ (message "")
+ (org-clock-resolve-clock
+ clock (cond
+ ((or (eq ch ?C)
+ ;; If the time on the clock was less than a minute before
+ ;; the user went idle, and they've ask to subtract all the
+ ;; time...
+ start-over)
+ nil)
+ (subtractp
+ (or last-valid last-modified))
+ ((= keep default)
+ 'now)
+ (t
+ (time-add (or last-valid last-modified)
+ (seconds-to-time (* 60 keep)))))
+ (or last-valid last-modified)
+ (memq ch '(?K ?S))
+ (and start-over (not (memq ch '(?K ?S))))
+ fail-quietly))))
+
+(defvar org-clock-resolving-clocks nil)
+(defvar org-clock-resolving-clocks-due-to-idleness nil)
+
+(defun org-resolve-clocks (&optional also-non-dangling-p
+ prompt-fn last-valid)
+ "Resolve all currently open org-mode clocks.
+If `also-non-dangling-p' is non-nil, also ask to resolve
+non-dangling (i.e., currently open and valid) clocks."
+ (interactive "P")
+ (unless org-clock-resolving-clocks
+ (let ((org-clock-resolving-clocks t))
+ (dolist (file org-agenda-files)
+ (let ((clocks (org-find-open-clocks file)))
+ (dolist (clock clocks)
+ (let ((dangling (or (not (org-clock-is-active))
+ (/= (car clock) org-clock-marker))))
+ (unless (and (not dangling) (not also-non-dangling-p))
+ (org-clock-resolve
+ clock
+ (or prompt-fn
+ (function
+ (lambda (clock)
+ (format
+ "Dangling clock from %d mins ago"
+ (floor
+ (/ (- (time-to-seconds (current-time))
+ (time-to-seconds
+ (with-current-buffer (marker-buffer (car clock))
+ (nth 5 (file-attributes (buffer-file-name))))))
+ 60))))))
+ last-valid)))))))))
+
+(defcustom org-clock-idle-time nil
+ "When non-nil, resolve open clocks if the user is idle more than X minutes."
+ :group 'org-clock
+ :type '(choice
+ (const :tag "Never" nil)
+ (integer :tag "After N minutes")))
+
+(defun org-emacs-idle-seconds ()
+ "Return the current Emacs idle time in seconds, or nil if not idle."
+ (let ((idle-time (current-idle-time)))
+ (if idle-time
+ (time-to-seconds idle-time)
+ 0)))
+
+(defun org-mac-idle-seconds ()
+ "Return the current Mac idle time in seconds"
+ (string-to-number (shell-command-to-string "ioreg -c IOHIDSystem | perl -ane 'if (/Idle/) {$idle=(pop @F)/1000000000; print $idle; last}'")))
+
+(defun org-user-idle-seconds ()
+ "Return the number of seconds the user has been idle for.
+This routine returns a floating point number."
+ (if (eq system-type 'darwin)
+ (let ((emacs-idle (org-emacs-idle-seconds)))
+ ;; If Emacs has been idle for longer than the user's
+ ;; `org-clock-idle-time' value, check whether the whole system has
+ ;; really been idle for that long.
+ (if (> emacs-idle (* 60 org-clock-idle-time))
+ (min emacs-idle (org-mac-idle-seconds))
+ emacs-idle))
+ (org-emacs-idle-seconds)))
+
+(defun org-resolve-clocks-if-idle ()
+ "Resolve all currently open org-mode clocks.
+This is performed after `org-clock-idle-time' minutes, to check
+if the user really wants to stay clocked in after being idle for
+so long."
+ (when (and org-clock-idle-time (not org-clock-resolving-clocks))
+ (let ((idle (org-user-idle-seconds))
+ (org-clock-resolving-clocks-due-to-idleness t))
+ (if (> idle (* 60 org-clock-idle-time))
+ (org-resolve-clocks
+ t
+ (function
+ (lambda (clock)
+ (format "Clocked in & idle for %d mins"
+ (/ (org-user-idle-seconds) 60))))
+ (time-subtract (current-time)
+ (seconds-to-time (org-user-idle-seconds))))))))
+
+(defvar org-clock-clocking-in nil)
+
(defun org-clock-in (&optional select)
"Start the clock on the current item.
If necessary, clock-out of the currently active clock.
@@ -505,8 +797,20 @@ the clocking selection, associated with the letter `d'."
(interactive "P")
(setq org-clock-notification-was-shown nil)
(catch 'abort
- (let ((interrupting (marker-buffer org-clock-marker))
- ts selected-task target-pos (msg-extra ""))
+ (let ((interrupting (and (not org-clock-resolving-clocks-due-to-idleness)
+ (marker-buffer org-clock-marker)))
+ ts selected-task target-pos (msg-extra "")
+ (left-over (and (not org-clock-resolving-clocks)
+ org-clock-left-over-time)))
+ (setq org-clock-left-over-time nil)
+ (unless org-clock-clocking-in
+ (let ((org-clock-clocking-in t))
+ (org-resolve-clocks) ; first check if any clocks are dangling
+ ;; If the resolution resulted in a new clock being started, then
+ ;; abort this clock in.
+ (if (and (not interrupting)
+ (marker-buffer org-clock-marker))
+ (throw 'abort nil))))
(when (equal select '(4))
(setq selected-task (org-clock-select-task "Clock-in on task: "))
(if selected-task
@@ -604,7 +908,15 @@ the clocking selection, associated with the letter `d'."
(setq org-clock-effort (org-get-effort))
(setq org-clock-total-time (org-clock-sum-current-item
(org-clock-get-sum-start)))
- (setq org-clock-start-time (current-time))
+ (setq org-clock-start-time
+ (or (and left-over
+ (y-or-n-p
+ (format
+ "You stopped another clock %d mins ago; start this one from then? "
+ (/ (- (time-to-seconds (current-time))
+ (time-to-seconds left-over)) 60)))
+ left-over)
+ (current-time)))
(setq ts (org-insert-time-stamp org-clock-start-time
'with-hm 'inactive))))
(move-marker org-clock-marker (point) (buffer-base-buffer))
@@ -616,8 +928,16 @@ the clocking selection, associated with the letter `d'."
(setq global-mode-string
(append global-mode-string '(org-mode-line-string))))
(org-clock-update-mode-line)
+ (when org-clock-mode-line-timer
+ (cancel-timer org-clock-mode-line-timer)
+ (setq org-clock-mode-line-timer nil))
(setq org-clock-mode-line-timer
(run-with-timer 60 60 'org-clock-update-mode-line))
+ (when org-clock-idle-timer
+ (cancel-timer org-clock-idle-timer)
+ (setq org-clock-idle-timer nil))
+ (setq org-clock-idle-timer
+ (run-with-timer 60 60 'org-resolve-clocks-if-idle))
(message "Clock starts at %s - %s" ts msg-extra)
(run-hooks 'org-clock-in-hook)))))))
@@ -748,7 +1068,7 @@ line and position cursor in that line."
(and (re-search-forward org-property-end-re nil t)
(goto-char (match-beginning 0))))))))
-(defun org-clock-out (&optional fail-quietly)
+(defun org-clock-out (&optional fail-quietly at-time)
"Stop the currently running clock.
If there is no running clock, throw an error, unless FAIL-QUIETLY is set."
(interactive)
@@ -769,7 +1089,8 @@ If there is no running clock, throw an error, unless FAIL-QUIETLY is set."
(goto-char (match-end 0))
(delete-region (point) (point-at-eol))
(insert "--")
- (setq te (org-insert-time-stamp (current-time) 'with-hm 'inactive))
+ (setq te (org-insert-time-stamp (or at-time (current-time))
+ 'with-hm 'inactive))
(setq s (- (org-float-time (apply 'encode-time (org-parse-time-string te)))
(org-float-time (apply 'encode-time (org-parse-time-string ts))))
h (floor (/ s 3600))
@@ -791,6 +1112,9 @@ If there is no running clock, throw an error, unless FAIL-QUIETLY is set."
(when org-clock-mode-line-timer
(cancel-timer org-clock-mode-line-timer)
(setq org-clock-mode-line-timer nil))
+ (when org-clock-idle-timer
+ (cancel-timer org-clock-idle-timer)
+ (setq org-clock-idle-timer nil))
(setq global-mode-string
(delq 'org-mode-line-string global-mode-string))
(when org-clock-out-switch-to-state
diff --git a/lisp/org.el b/lisp/org.el
index d3d886f..d895e2f 100644
--- a/lisp/org.el
+++ b/lisp/org.el
@@ -3141,6 +3141,8 @@ If TABLE-TYPE is non-nil, also check for table.el-type tables."
(declare-function org-clock-save-markers-for-cut-and-paste "org-clock"
(beg end))
(declare-function org-clock-update-mode-line "org-clock" ())
+(declare-function org-resolve-clocks "org-clock"
+ (&optional also-non-dangling-p prompt last-valid))
(defvar org-clock-start-time)
(defvar org-clock-marker (make-marker)
"Marker recording the last clock-in.")
@@ -3158,7 +3160,7 @@ The return value is actually the clock marker."
org-clock-goto org-clock-sum org-clock-display
org-clock-remove-overlays org-clock-report
org-clocktable-shift org-dblock-write:clocktable
- org-get-clocktable)))
+ org-get-clocktable org-resolve-clocks)))
(defun org-clock-update-time-maybe ()
"If this is a CLOCK line, update it and return t.
--
1.6.5
[-- Attachment #3: Type: text/plain, Size: 204 bytes --]
_______________________________________________
Emacs-orgmode mailing list
Remember: use `Reply All' to send replies to the list.
Emacs-orgmode@gnu.org
http://lists.gnu.org/mailman/listinfo/emacs-orgmode
^ permalink raw reply related [flat|nested] 16+ messages in thread
* Re: Patch for resolving "away time" when clocked in
2009-10-16 7:03 Patch for resolving "away time" when clocked in John Wiegley
@ 2009-10-16 14:25 ` Jeff Kowalczyk
2009-10-16 16:32 ` John Wiegley
2009-10-16 15:59 ` Patch for resolving "away time" when clocked in Gregory J. Grubbs
` (2 subsequent siblings)
3 siblings, 1 reply; 16+ messages in thread
From: Jeff Kowalczyk @ 2009-10-16 14:25 UTC (permalink / raw)
To: emacs-orgmode
John Wiegley <jwiegley <at> gmail.com> writes:
> Looking for anyone who uses Org's time clocking facilities and is
> willing to test this with me. I've been using it for a couple of days
> now. The functionality is based on the way the commercial app
> OfficeTime handles idleness.
Hi John,
This is interesting and useful, and the manual section describing how it works
is clear.
I applied the patch against 93f396, and on Emacs 23.1 I get an error when
clocking in. The error happens the first clock-in for each Emacs session, but
the 'invalid timer' message shows for subsequent clock-in/out.
Thanks,
Jeff
Debugger entered--Lisp error: (error "Invalid timer")
signal(error ("Invalid timer"))
error("Invalid timer")
cancel-timer(1)
byte-code("(snip)
[org-clock-resolving-clocks-due-to-idleness org-clock-marker
org-clock-resolving-clocks org-clock-left-over-time left-over msg-extra
marker-buffer nil "" t org-resolve-clocks throw abort (4)
org-clock-select-task "Clock-in on task: " copy-marker error "Abort"
marker-position org-clock-out (16) org-clock-mark-default-task
org-on-heading-p point-at-bol 0 run-hooks org-clock-in-prepare-hook
org-base-buffer org-back-to-heading org-clock-history-push functionp
looking-at match-string 2 org-todo "[ ]*" "\\>" 4 org-trim 1 "???"
org-propertize face org-clock-find-position "^[ ]* " "
\\[\\([0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\}" " +\\sw+.?
+[012][0-9]:[0-5][0-9]\\)\\][ ]*$" message "Matched %s" ...] 7)
org-clock-in(nil)
call-interactively(org-clock-in nil nil)
The org-related parts of my config:
(add-to-list 'load-path "~/.emacs.d/vendor/org-mode/lisp")
(add-to-list 'load-path "~/.emacs.d/vendor/org-mode/contrib/lisp")
(add-to-list 'auto-mode-alist '("\\.org\\'" . org-mode))
(require 'org-install)
(setq org-clock-persist t)
(org-clock-persistence-insinuate)
(global-set-key "\C-cl" 'org-store-link)
(global-set-key "\C-ca" 'org-agenda)
(setq org-log-done 'time)
(setq org-special-ctrl-k t)
(org-remember-insinuate)
(setq org-directory "~/org/")
(setq org-default-notes-file (concat org-directory "/notes.org"))
(define-key global-map "\C-cr" 'org-remember)
(setq org-clock-into-drawer t)
(setq org-clock-clocktable-default-properties '(:maxlevel 2 :scope subtree))
(defun yas/org-very-safe-expand ()
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: Re: Patch for resolving "away time" when clocked in
2009-10-16 14:25 ` Jeff Kowalczyk
@ 2009-10-16 16:32 ` John Wiegley
2009-10-16 17:02 ` Jeff Kowalczyk
0 siblings, 1 reply; 16+ messages in thread
From: John Wiegley @ 2009-10-16 16:32 UTC (permalink / raw)
To: Jeff Kowalczyk; +Cc: emacs-orgmode
On Oct 16, 2009, at 10:25 AM, Jeff Kowalczyk wrote:
> I applied the patch against 93f396, and on Emacs 23.1 I get an error
> when
> clocking in. The error happens the first clock-in for each Emacs
> session, but
> the 'invalid timer' message shows for subsequent clock-in/out.
In org-clock.el, on line 236, please change that line to:
(defvar org-clock-left-over-time nil
And see if this resolves your problem. I am unable to reproduce the
cancel-timer failure here. Please load org-clock.el into Emacs and
type M-x eval-buffer and then trigger it again, that I may see the
complete stack trace.
Thank you,
John
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: Patch for resolving "away time" when clocked in
2009-10-16 16:32 ` John Wiegley
@ 2009-10-16 17:02 ` Jeff Kowalczyk
2009-10-16 17:41 ` John Wiegley
0 siblings, 1 reply; 16+ messages in thread
From: Jeff Kowalczyk @ 2009-10-16 17:02 UTC (permalink / raw)
To: emacs-orgmode
John Wiegley <jwiegley <at> gmail.com> writes:
>
> On Oct 16, 2009, at 10:25 AM, Jeff Kowalczyk wrote:
>
> > I applied the patch against 93f396, and on Emacs 23.1 I get an error
> > when clocking in. The error happens the first clock-in for
> > each Emacs session, but the 'invalid timer' message shows for
> > subsequent clock-in/out.
>
> In org-clock.el, on line 236, please change that line to:
>
> (defvar org-clock-left-over-time nil
>
> And see if this resolves your problem.
Thank you. With that change there are no more visible errors or backtraces.
> I am unable to reproduce the
> cancel-timer failure here. Please load org-clock.el into Emacs and
> type M-x eval-buffer and then trigger it again, that I may see the
> complete stack trace.
No error anymore as mentioned above.
I'm not sure I'm triggering the new functionality correctly. In the scratch
buffer, I eval'd:
(setq org-clock-idle-time 1)
Next, I created an Org buffer with one TODO file, and clocked in, and took my
hands off the computer. I waited three minutes, then clocked out.
* Worklog
** TODO Task 1
:LOGBOOK:
CLOCK: [2009-10-16 Fri 09:42]--[2009-10-16 Fri 09:46] => 0:04
:END:
Was I supposed to be prompted to resolve some minutes when clocking out after
the org-clock-idle-time had elapsed?
Thanks,
Jeff
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: Re: Patch for resolving "away time" when clocked in
2009-10-16 17:02 ` Jeff Kowalczyk
@ 2009-10-16 17:41 ` John Wiegley
2009-10-16 17:52 ` John Wiegley
0 siblings, 1 reply; 16+ messages in thread
From: John Wiegley @ 2009-10-16 17:41 UTC (permalink / raw)
To: Jeff Kowalczyk; +Cc: emacs-orgmode
On Oct 16, 2009, at 1:02 PM, Jeff Kowalczyk wrote:
>> I am unable to reproduce the
>> cancel-timer failure here. Please load org-clock.el into Emacs and
>> type M-x eval-buffer and then trigger it again, that I may see the
>> complete stack trace.
>
> No error anymore as mentioned above.
>
> I'm not sure I'm triggering the new functionality correctly. In the
> scratch
> buffer, I eval'd:
>
> (setq org-clock-idle-time 1)
Ah, I think I see where the cancel-timer bug came from. You had set
org-clock-idle-timer to 1, rather than org-clock-idle-time (only the
latter is a user-customizable variable).
> Next, I created an Org buffer with one TODO file, and clocked in,
> and took my
> hands off the computer. I waited three minutes, then clocked out.
>
> * Worklog
> ** TODO Task 1
> :LOGBOOK:
> CLOCK: [2009-10-16 Fri 09:42]--[2009-10-16 Fri 09:46] => 0:04
> :END:
>
> Was I supposed to be prompted to resolve some minutes when clocking
> out after
> the org-clock-idle-time had elapsed?
At the moment, the auto-resolver only checks files which are referred
to by org-agenda-files. It does not scan the entire buffer list
looking for any org-mode buffer. Do you think it should do the latter
instead?
John
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: Re: Patch for resolving "away time" when clocked in
2009-10-16 17:41 ` John Wiegley
@ 2009-10-16 17:52 ` John Wiegley
2009-10-16 18:13 ` Patch for resolving Jeff Kowalczyk
0 siblings, 1 reply; 16+ messages in thread
From: John Wiegley @ 2009-10-16 17:52 UTC (permalink / raw)
To: John Wiegley; +Cc: Jeff Kowalczyk, emacs-orgmode
On Oct 16, 2009, at 1:41 PM, John Wiegley wrote:
> At the moment, the auto-resolver only checks files which are
> referred to by org-agenda-files. It does not scan the entire buffer
> list looking for any org-mode buffer. Do you think it should do the
> latter instead?
I have a better answer for this:
1. For the checking idle time, I just check the currently active
clock's file, wherever that happens to be.
2. For resolving clocks on clock-in, I'll scan all agenda-files and
open org-mode buffers.
John
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: Patch for resolving
2009-10-16 17:52 ` John Wiegley
@ 2009-10-16 18:13 ` Jeff Kowalczyk
0 siblings, 0 replies; 16+ messages in thread
From: Jeff Kowalczyk @ 2009-10-16 18:13 UTC (permalink / raw)
To: emacs-orgmode
John Wiegley <jwiegley <at> gmail.com> writes:
> On Oct 16, 2009, at 1:41 PM, John Wiegley wrote:
>
> > At the moment, the auto-resolver only checks files which are
> > referred to by org-agenda-files. It does not scan the entire buffer
> > list looking for any org-mode buffer. Do you think it should do the
> > latter instead?
>
> I have a better answer for this:
>
> 1. For the checking idle time, I just check the currently active
> clock's file, wherever that happens to be.
>
> 2. For resolving clocks on clock-in, I'll scan all agenda-files and
> open org-mode buffers.
>
> John
Thanks, that plan sounds ideal for the way I use Org, i.e. without the agenda,
and one or more open buffers in org-mode for clocking time.
I actually haven't gotten around to using the agenda yet, because (and this is
purely laziness on this user's part), I never got it to display gridded time for
my completed worklogs in the form below (i.e. few things are <SCHEDULED> ahead
of time):
* Worklog for ACME
** DONE Task 1 :tagA:
CLOSED: [2009-10-16 Fri 11:06]
:LOGBOOK:
CLOCK: [2009-10-16 Fri 09:42]--[2009-10-16 Fri 09:46] => 0:04
:END:
Describe.
** DONE Task 2 :tagA:tagB:
CLOSED: [2009-10-16 Fri 11:06]
:LOGBOOK:
CLOCK: [2009-10-16 Fri 10:00]--[2009-10-16 Fri 10:43] => 0:43
CLOCK: [2009-10-16 Fri 09:54]--[2009-10-16 Fri 09:56] => 0:02
:END:
Notes taken.
** TODO Task 3 :tagA:
:LOGBOOK:
CLOCK: [2009-10-16 Fri 11:07]
CLOCK: [2009-10-16 Fri 10:43]--[2009-10-16 Fri 10:44] => 0:01
:END:
In progress...
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: Patch for resolving "away time" when clocked in
2009-10-16 7:03 Patch for resolving "away time" when clocked in John Wiegley
2009-10-16 14:25 ` Jeff Kowalczyk
@ 2009-10-16 15:59 ` Gregory J. Grubbs
2009-10-16 16:45 ` John Wiegley
2009-10-19 15:41 ` Bernt Hansen
2009-10-20 11:57 ` James TD Smith
3 siblings, 1 reply; 16+ messages in thread
From: Gregory J. Grubbs @ 2009-10-16 15:59 UTC (permalink / raw)
To: emacs-orgmode
John Wiegley <jwiegley@gmail.com> writes:
> Looking for anyone who uses Org's time clocking facilities and is
> willing to test this with me. I've been using it for a couple of days
> now. The functionality is based on the way the commercial app
> OfficeTime handles idleness.
Wow. Your patch helped me clean up a multitude of dangling clocks.
Thank you!
One suggestion: I think when your code stops on a clock and prompts to
keep/subtract/cancel, it should expand the drawer (for those of us who
use drawers).
-Greg
(I know, there's probably already a variable like
org-expand-drawers-when-running-a-patched-version)
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: Re: Patch for resolving "away time" when clocked in
2009-10-16 15:59 ` Patch for resolving "away time" when clocked in Gregory J. Grubbs
@ 2009-10-16 16:45 ` John Wiegley
2009-10-16 18:09 ` Gregory J. Grubbs
0 siblings, 1 reply; 16+ messages in thread
From: John Wiegley @ 2009-10-16 16:45 UTC (permalink / raw)
To: Gregory J. Grubbs; +Cc: emacs-orgmode
[-- Attachment #1: Type: text/plain, Size: 644 bytes --]
On Oct 16, 2009, at 11:59 AM, Gregory J. Grubbs wrote:
> One suggestion: I think when your code stops on a clock and prompts to
> keep/subtract/cancel, it should expand the drawer (for those of us who
> use drawers).
I've added this to the version of the patch below.
As another question:
When attempting to clock into A, and org-resolve-clocks finds a
dangling clock in B, and the user presses "k" or "s" (i.e., not K or
S), do you expect it to clock you into A or into B when the resolution
is done? Right now K will continue the clock-in to A, but k will
abort the clock-in to A and clock you into B to resume that task.
John
[-- Attachment #2: 0001-Added-clock-resolution-logic.patch --]
[-- Type: application/octet-stream, Size: 23881 bytes --]
From 46222b3cff81bd0df53fd2c91b0548d92ef57a22 Mon Sep 17 00:00:00 2001
From: John Wiegley <johnw@newartisans.com>
Date: Tue, 13 Oct 2009 03:38:16 -0400
Subject: [PATCH] Added "clock resolution" logic
Allows the user to find and resolve dangling clock entries, as well as
notify them if they have been idle while clocked in.
There are several ways to resolve an open clock (whether dangling or
idle):
k - Keep X minutes of the time, default being all of the away time
(for dangling entries, the away time is computed from the last
time the org file was saved)
K - Keep X minutes, then immediately clock out
s - Subtract all of the idle/away minutes and resume clock from now
S - Subtract all of the idle/away minutes, and clock out
C - Cancel the clock
Note that some of these keys work without affecting the current clock,
unless the entry being operated on happens to be the current clock.
This means if you clocked into a different task, ran M-x
org-resolve-clocks and found a dangling entry, you could resolve it
using S without interrupting the current clock. If you resolved with k
or s, however, it would clock you out of the active clock, and back into
the one being resolved, since those keys assume a desire to resume the
task that was orphaned.
---
doc/org.texi | 71 +++++++++++-
lisp/org-clock.el | 346 ++++++++++++++++++++++++++++++++++++++++++++++++++++-
lisp/org.el | 4 +-
3 files changed, 413 insertions(+), 8 deletions(-)
diff --git a/doc/org.texi b/doc/org.texi
index 538aba8..313c6cd 100644
--- a/doc/org.texi
+++ b/doc/org.texi
@@ -231,6 +231,7 @@ Dates and Times
* Creating timestamps:: Commands which insert timestamps
* Deadlines and scheduling:: Planning your work
* Clocking work time:: Tracking how long you spend on a task
+* Resolving idle time::
* Effort estimates:: Planning work effort in advance
* Relative timer:: Notes with a running timer
@@ -4703,6 +4704,7 @@ is used in a much wider sense.
* Creating timestamps:: Commands which insert timestamps
* Deadlines and scheduling:: Planning your work
* Clocking work time:: Tracking how long you spend on a task
+* Resolving idle time::
* Effort estimates:: Planning work effort in advance
* Relative timer:: Notes with a running timer
@end menu
@@ -5220,7 +5222,7 @@ subtree, with dates shifted in each copy. The command @kbd{C-c C-x c} was
created for this purpose, it is described in @ref{Structure editing}.
-@node Clocking work time, Effort estimates, Deadlines and scheduling, Dates and Times
+@node Clocking work time, Resolving idle time, Deadlines and scheduling, Dates and Times
@section Clocking work time
Org mode allows you to clock the time you spend on specific tasks in a
@@ -5405,7 +5407,72 @@ The @kbd{l} key may be used in the timeline (@pxref{Timeline}) and in
the agenda (@pxref{Weekly/daily agenda}) to show which tasks have been
worked on or closed during a day.
-@node Effort estimates, Relative timer, Clocking work time, Dates and Times
+@node Resolving idle time, Effort estimates, Clocking work time, Dates and Times
+@section Resolving idle time
+@cindex resolve idle time
+
+@cindex idle, resolve, dangling
+If you clock in on a work item, and then walk away from your
+computer---perhaps to take a phone call---you often need to ``resolve'' the
+time you were away by either subtracting it from the current clock, or
+applying it to another one.
+
+@vindex org-clock-idle-time
+By customizing the variable @code{org-clock-idle-time} to some integer, such
+as 10 or 15, Emacs can alert you when you get back to your computer after
+being idle for that many minutes@footnote{On computers using Mac OS X,
+idleness is based on actual user idleness, not just Emacs' idle time.}, and
+ask what you want to do with the idle time. There will be a question waiting
+for you when you get back, indicating how much idle time has passed
+(constantly updated with the current amount), as well as a set of choices to
+correct the discrepancy:
+
+@table @kbd
+@item k
+To keep some or all of the minutes and stay clocked in, press @key{k}. Org
+will ask how many of the minutes to keep. Press @key{RET} to keep them all,
+effectively changing nothing, or enter a number to keep that many minutes.
+@item K
+If you use the shift key and press @key{K}, it will keep however many minutes
+you request and then immediately clock out of that task. If you keep all of
+the minutes, this is the same as just clocking out of the current task.
+@item s
+To keep none of the minutes, use @key{s} to subtract all the away time from
+the clock, and then check back in from the moment you returned.
+@item S
+To keep none of the minutes and just clock out at the start of the away time,
+use the shift key and press @key{S}. Remember that using shift will always
+leave you clocked out, no matter which option you choose.
+@item C
+To cancel the clock altogether, use @key{C}. Note that if instead of
+cancelling you subtract the away time, and the resulting clock amount is less
+than a minute, the clock will still be cancelled rather than clutter up the
+log with an empty entry.
+@end table
+
+What if you subtracted those away minutes from the current clock, and now
+want to apply them to a new clock? Simply clock in to any task immediately
+after the subtraction. Org will notice that you have subtracted time ``on
+the books'', so to speak, and will ask if you want to apply those minutes to
+the next task you clock in on.
+
+There is one other instance when this clock resolution magic occurs. Say you
+were clocked in and hacking away, and suddenly your cat chased a mouse who
+scared a hamster that crashed into your UPS's power button! You suddenly
+lose all your buffers, but thanks to auto-save you still have your recent Org
+mode changes, including your last clock in.
+
+If you restart Emacs and clock into any task, Org will notice that you have a
+dangling clock which was never clocked out from your last session. Using the
+Org file's last modified time as the beginning of the ``away'' period, Org
+will ask how you want to resolve that unaccounted-for time. The logic and
+behavior is identical to dealing with away time due to idleness, it's just
+happening due to a recovery event rather than a set amount of idle time.
+
+You can also check all the files visited by your Org agenda for dangling
+clocks at any time using @kbd{M-x org-resolve-clocks}.
+
+@node Effort estimates, Relative timer, Resolving idle time, Dates and Times
@section Effort estimates
@cindex effort estimates
diff --git a/lisp/org-clock.el b/lisp/org-clock.el
index 97e5552..1f424f1 100644
--- a/lisp/org-clock.el
+++ b/lisp/org-clock.el
@@ -228,10 +228,14 @@ to add an effort property.")
(put 'org-mode-line-string 'risky-local-variable t)
(defvar org-clock-mode-line-timer nil)
+(defvar org-clock-idle-timer nil)
(defvar org-clock-heading "")
(defvar org-clock-heading-for-remember "")
(defvar org-clock-start-time "")
+(defvar org-clock-left-over-time nil
+ "If non-nil, user cancelled a clock; this is when leftover time started.")
+
(defvar org-clock-effort ""
"Effort estimate of the currently clocking task")
@@ -495,6 +499,306 @@ Use alsa's aplay tool if available."
(defvar org-clock-mode-line-entry nil
"Information for the modeline about the running clock.")
+(defun org-find-open-clocks (file)
+ "Search through the given file and find all open clocks."
+ (let ((buf (or (get-file-buffer file)
+ (find-file-noselect file)))
+ clocks)
+ (with-current-buffer buf
+ (save-excursion
+ (goto-char (point-min))
+ (while (re-search-forward "CLOCK: \\(\\[.*?\\]\\)$" nil t)
+ (push (cons (copy-marker (1- (match-end 1)) t)
+ (org-time-string-to-time (match-string 1))) clocks))))
+ clocks))
+
+(defsubst org-is-active-clock (clock)
+ "Return t if CLOCK is the currently active clock."
+ (and (org-clock-is-active)
+ (= org-clock-marker (car clock))))
+
+(defmacro org-with-clock-position (clock &rest forms)
+ "Evaluate FORMS with CLOCK as the current active clock."
+ `(with-current-buffer (marker-buffer (car ,clock))
+ (save-excursion
+ (save-restriction
+ (widen)
+ (goto-char (car ,clock))
+ (beginning-of-line)
+ ,@forms))))
+
+(put 'org-with-clock-position 'lisp-indent-function 1)
+
+(defmacro org-with-clock (clock &rest forms)
+ "Evaluate FORMS with CLOCK as the current active clock.
+This macro also protects the current active clock from being altered."
+ `(org-with-clock-position ,clock
+ (let ((org-clock-start-time (cdr ,clock))
+ (org-clock-total-time)
+ (org-clock-history)
+ (org-clock-effort)
+ (org-clock-marker (car ,clock))
+ (org-clock-hd-marker (save-excursion
+ (outline-back-to-heading t)
+ (point-marker))))
+ ,@forms)))
+
+(put 'org-with-clock 'lisp-indent-function 1)
+
+(defsubst org-clock-clock-in (clock &optional resume)
+ "Clock in to the clock located by CLOCK.
+If necessary, clock-out of the currently active clock."
+ (org-with-clock-position clock
+ (let ((org-clock-in-resume (or resume org-clock-in-resume)))
+ (org-clock-in))))
+
+(defsubst org-clock-clock-out (clock &optional fail-quietly at-time)
+ "Clock out of the clock located by CLOCK."
+ (let ((temp (copy-marker (car clock)
+ (marker-insertion-type (car clock)))))
+ (if (org-is-active-clock clock)
+ (org-clock-out fail-quietly at-time)
+ (org-with-clock clock
+ (org-clock-out fail-quietly at-time)))
+ (setcar clock temp)))
+
+(defsubst org-clock-clock-cancel (clock)
+ "Cancel the clock located by CLOCK."
+ (let ((temp (copy-marker (car clock)
+ (marker-insertion-type (car clock)))))
+ (if (org-is-active-clock clock)
+ (org-clock-cancel)
+ (org-with-clock clock
+ (org-clock-cancel)))
+ (setcar clock temp)))
+
+(defun org-clock-resolve-clock (clock resolve-to last-valid &optional
+ close-p restart-p fail-quietly)
+ "Resolve `CLOCK' given the time `RESOLVE-TO', and the present.
+`CLOCK' is a cons cell of the form (MARKER START-TIME).
+This routine can do one of many things:
+
+ if `RESOLVE-TO' is nil
+ if `CLOSE-P' is non-nil, give an error
+ if this clock is the active clock, cancel it
+ else delete the clock line (as if it never happened)
+ if `RESTART-P' is non-nil, start a new clock
+
+ else if `RESOLVE-TO' is the symbol `now'
+ if `RESTART-P' is non-nil, give an error
+ if `CLOSE-P' is non-nil, clock out the entry and
+ if this clock is the active clock, stop it
+ else if this clock is the active clock, do nothing
+ else if there is no active clock, resume this clock
+ else ask to cancel the active clock, and if so,
+ resume this clock after cancelling it
+
+ else if `RESOLVE-TO' is some date in the future
+ give an error about `RESOLVE-TO' being invalid
+
+ else if `RESOLVE-TO' is some date in the past
+ if `RESTART-P' is non-nil, give an error
+ if `CLOSE-P' is non-nil, enter a closing time and
+ if this clock is the active clock, stop it
+ else if this clock is the active clock, enter a
+ closing time, stop the current clock, then
+ start a new clock for the same item
+ else just enter a closing time for this clock
+ and then start a new clock for the same item"
+ (cond
+ ((null resolve-to)
+ (org-clock-clock-cancel clock)
+ (if restart-p
+ (org-clock-clock-in clock)))
+
+ ((eq resolve-to 'now)
+ (if restart-p
+ (error "RESTART-P is not valid here"))
+ (if close-p
+ (org-clock-clock-out clock fail-quietly)
+ (unless (org-is-active-clock clock)
+ (org-clock-clock-in clock t))))
+
+ ((not (time-less-p resolve-to (current-time)))
+ (error "RESOLVE-TO must refer to a time in the past"))
+
+ (t
+ (if restart-p
+ (error "RESTART-P is not valid here"))
+ (if close-p
+ (progn
+ (org-clock-clock-out clock fail-quietly resolve-to)
+ (setq org-clock-left-over-time last-valid))
+ (org-clock-clock-out clock fail-quietly resolve-to)
+ (org-clock-clock-in clock)))))
+
+(defun org-clock-resolve (clock &optional prompt-fn last-valid fail-quietly)
+ "Resolve an open org-mode clock.
+An open clock was found, with `dangling' possibly being non-nil.
+If this function was invoked with a prefix argument, non-dangling
+open clocks are ignored. The given clock requires some sort of
+user intervention to resolve it, either because a clock was left
+dangling or due to an idle timeout. The clock resolution can
+either be:
+
+ (a) deleted, the user doesn't care about the clock
+ (b) restarted from the current time (if no other clock is open)
+ (c) closed, giving the clock X minutes
+ (d) closed and then restarted
+ (e) resumed, as if the user had never left
+
+The format of clock is (CONS MARKER START-TIME), where MARKER
+identifies the buffer and position the clock is open at (and
+thus, the heading it's under), and START-TIME is when the clock
+was started."
+ (assert clock)
+ (let* ((ch (progn
+ (unless org-clock-resolving-clocks-due-to-idleness
+ (org-with-clock clock
+ (org-clock-goto))
+ (with-current-buffer (marker-buffer (car clock))
+ (goto-char (car clock))
+ (if org-clock-into-drawer
+ (ignore-errors
+ (outline-flag-region (save-excursion
+ (outline-back-to-heading t)
+ (search-forward ":LOGBOOK:")
+ (goto-char (match-beginning 0)))
+ (save-excursion
+ (outline-back-to-heading t)
+ (search-forward ":LOGBOOK:")
+ (search-forward ":END:")
+ (goto-char (match-end 0)))
+ nil)))))
+ (let (char-pressed)
+ (while (null char-pressed)
+ (setq char-pressed
+ (read-char (concat (funcall prompt-fn clock)
+ " [(kK)eep (sS)ubtract (C)ancel]? ")
+ nil 45)))
+ char-pressed)))
+ (last-modified (unless last-valid
+ (with-current-buffer (marker-buffer (car clock))
+ (nth 5 (file-attributes (buffer-file-name))))))
+ (default (floor (/ (time-to-seconds
+ (time-subtract (current-time)
+ (or last-valid
+ last-modified))) 60)))
+ (keep (and (memq ch '(?k ?K))
+ (read-number (format "Keep how many minutes (since %s)? "
+ (if last-valid
+ "idle" "org last saved"))
+ default)))
+ (subtractp (memq ch '(?s ?S)))
+ (barely-started-p (< (- (time-to-seconds (or last-valid last-modified))
+ (time-to-seconds (cdr clock))) 45))
+ (start-over (and subtractp barely-started-p)))
+ (if (or (null ch)
+ (not (memq ch '(?k ?K ?s ?S ?C))))
+ (message "")
+ (org-clock-resolve-clock
+ clock (cond
+ ((or (eq ch ?C)
+ ;; If the time on the clock was less than a minute before
+ ;; the user went idle, and they've ask to subtract all the
+ ;; time...
+ start-over)
+ nil)
+ (subtractp
+ (or last-valid last-modified))
+ ((= keep default)
+ 'now)
+ (t
+ (time-add (or last-valid last-modified)
+ (seconds-to-time (* 60 keep)))))
+ (or last-valid last-modified)
+ (memq ch '(?K ?S))
+ (and start-over (not (memq ch '(?K ?S))))
+ fail-quietly))))
+
+(defvar org-clock-resolving-clocks nil)
+(defvar org-clock-resolving-clocks-due-to-idleness nil)
+
+(defun org-resolve-clocks (&optional also-non-dangling-p
+ prompt-fn last-valid)
+ "Resolve all currently open org-mode clocks.
+If `also-non-dangling-p' is non-nil, also ask to resolve
+non-dangling (i.e., currently open and valid) clocks."
+ (interactive "P")
+ (unless org-clock-resolving-clocks
+ (let ((org-clock-resolving-clocks t))
+ (dolist (file org-agenda-files)
+ (let ((clocks (org-find-open-clocks file)))
+ (dolist (clock clocks)
+ (let ((dangling (or (not (org-clock-is-active))
+ (/= (car clock) org-clock-marker))))
+ (unless (and (not dangling) (not also-non-dangling-p))
+ (org-clock-resolve
+ clock
+ (or prompt-fn
+ (function
+ (lambda (clock)
+ (format
+ "Dangling clock from %d mins ago"
+ (floor
+ (/ (- (time-to-seconds (current-time))
+ (time-to-seconds
+ (with-current-buffer (marker-buffer (car clock))
+ (nth 5 (file-attributes (buffer-file-name))))))
+ 60))))))
+ last-valid)))))))))
+
+(defcustom org-clock-idle-time nil
+ "When non-nil, resolve open clocks if the user is idle more than X minutes."
+ :group 'org-clock
+ :type '(choice
+ (const :tag "Never" nil)
+ (integer :tag "After N minutes")))
+
+(defun org-emacs-idle-seconds ()
+ "Return the current Emacs idle time in seconds, or nil if not idle."
+ (let ((idle-time (current-idle-time)))
+ (if idle-time
+ (time-to-seconds idle-time)
+ 0)))
+
+(defun org-mac-idle-seconds ()
+ "Return the current Mac idle time in seconds"
+ (string-to-number (shell-command-to-string "ioreg -c IOHIDSystem | perl -ane 'if (/Idle/) {$idle=(pop @F)/1000000000; print $idle; last}'")))
+
+(defun org-user-idle-seconds ()
+ "Return the number of seconds the user has been idle for.
+This routine returns a floating point number."
+ (if (eq system-type 'darwin)
+ (let ((emacs-idle (org-emacs-idle-seconds)))
+ ;; If Emacs has been idle for longer than the user's
+ ;; `org-clock-idle-time' value, check whether the whole system has
+ ;; really been idle for that long.
+ (if (> emacs-idle (* 60 org-clock-idle-time))
+ (min emacs-idle (org-mac-idle-seconds))
+ emacs-idle))
+ (org-emacs-idle-seconds)))
+
+(defun org-resolve-clocks-if-idle ()
+ "Resolve all currently open org-mode clocks.
+This is performed after `org-clock-idle-time' minutes, to check
+if the user really wants to stay clocked in after being idle for
+so long."
+ (when (and org-clock-idle-time (not org-clock-resolving-clocks))
+ (let ((idle (org-user-idle-seconds))
+ (org-clock-resolving-clocks-due-to-idleness t))
+ (if (> idle (* 60 org-clock-idle-time))
+ (org-resolve-clocks
+ t
+ (function
+ (lambda (clock)
+ (format "Clocked in & idle for %d mins"
+ (/ (org-user-idle-seconds) 60))))
+ (time-subtract (current-time)
+ (seconds-to-time (org-user-idle-seconds))))))))
+
+(defvar org-clock-clocking-in nil)
+
(defun org-clock-in (&optional select)
"Start the clock on the current item.
If necessary, clock-out of the currently active clock.
@@ -505,8 +809,20 @@ the clocking selection, associated with the letter `d'."
(interactive "P")
(setq org-clock-notification-was-shown nil)
(catch 'abort
- (let ((interrupting (marker-buffer org-clock-marker))
- ts selected-task target-pos (msg-extra ""))
+ (let ((interrupting (and (not org-clock-resolving-clocks-due-to-idleness)
+ (marker-buffer org-clock-marker)))
+ ts selected-task target-pos (msg-extra "")
+ (left-over (and (not org-clock-resolving-clocks)
+ org-clock-left-over-time)))
+ (setq org-clock-left-over-time nil)
+ (unless org-clock-clocking-in
+ (let ((org-clock-clocking-in t))
+ (org-resolve-clocks) ; first check if any clocks are dangling
+ ;; If the resolution resulted in a new clock being started, then
+ ;; abort this clock in.
+ (if (and (not interrupting)
+ (marker-buffer org-clock-marker))
+ (throw 'abort nil))))
(when (equal select '(4))
(setq selected-task (org-clock-select-task "Clock-in on task: "))
(if selected-task
@@ -604,7 +920,15 @@ the clocking selection, associated with the letter `d'."
(setq org-clock-effort (org-get-effort))
(setq org-clock-total-time (org-clock-sum-current-item
(org-clock-get-sum-start)))
- (setq org-clock-start-time (current-time))
+ (setq org-clock-start-time
+ (or (and left-over
+ (y-or-n-p
+ (format
+ "You stopped another clock %d mins ago; start this one from then? "
+ (/ (- (time-to-seconds (current-time))
+ (time-to-seconds left-over)) 60)))
+ left-over)
+ (current-time)))
(setq ts (org-insert-time-stamp org-clock-start-time
'with-hm 'inactive))))
(move-marker org-clock-marker (point) (buffer-base-buffer))
@@ -616,8 +940,16 @@ the clocking selection, associated with the letter `d'."
(setq global-mode-string
(append global-mode-string '(org-mode-line-string))))
(org-clock-update-mode-line)
+ (when org-clock-mode-line-timer
+ (cancel-timer org-clock-mode-line-timer)
+ (setq org-clock-mode-line-timer nil))
(setq org-clock-mode-line-timer
(run-with-timer 60 60 'org-clock-update-mode-line))
+ (when org-clock-idle-timer
+ (cancel-timer org-clock-idle-timer)
+ (setq org-clock-idle-timer nil))
+ (setq org-clock-idle-timer
+ (run-with-timer 60 60 'org-resolve-clocks-if-idle))
(message "Clock starts at %s - %s" ts msg-extra)
(run-hooks 'org-clock-in-hook)))))))
@@ -748,7 +1080,7 @@ line and position cursor in that line."
(and (re-search-forward org-property-end-re nil t)
(goto-char (match-beginning 0))))))))
-(defun org-clock-out (&optional fail-quietly)
+(defun org-clock-out (&optional fail-quietly at-time)
"Stop the currently running clock.
If there is no running clock, throw an error, unless FAIL-QUIETLY is set."
(interactive)
@@ -769,7 +1101,8 @@ If there is no running clock, throw an error, unless FAIL-QUIETLY is set."
(goto-char (match-end 0))
(delete-region (point) (point-at-eol))
(insert "--")
- (setq te (org-insert-time-stamp (current-time) 'with-hm 'inactive))
+ (setq te (org-insert-time-stamp (or at-time (current-time))
+ 'with-hm 'inactive))
(setq s (- (org-float-time (apply 'encode-time (org-parse-time-string te)))
(org-float-time (apply 'encode-time (org-parse-time-string ts))))
h (floor (/ s 3600))
@@ -791,6 +1124,9 @@ If there is no running clock, throw an error, unless FAIL-QUIETLY is set."
(when org-clock-mode-line-timer
(cancel-timer org-clock-mode-line-timer)
(setq org-clock-mode-line-timer nil))
+ (when org-clock-idle-timer
+ (cancel-timer org-clock-idle-timer)
+ (setq org-clock-idle-timer nil))
(setq global-mode-string
(delq 'org-mode-line-string global-mode-string))
(when org-clock-out-switch-to-state
diff --git a/lisp/org.el b/lisp/org.el
index d3d886f..d895e2f 100644
--- a/lisp/org.el
+++ b/lisp/org.el
@@ -3141,6 +3141,8 @@ If TABLE-TYPE is non-nil, also check for table.el-type tables."
(declare-function org-clock-save-markers-for-cut-and-paste "org-clock"
(beg end))
(declare-function org-clock-update-mode-line "org-clock" ())
+(declare-function org-resolve-clocks "org-clock"
+ (&optional also-non-dangling-p prompt last-valid))
(defvar org-clock-start-time)
(defvar org-clock-marker (make-marker)
"Marker recording the last clock-in.")
@@ -3158,7 +3160,7 @@ The return value is actually the clock marker."
org-clock-goto org-clock-sum org-clock-display
org-clock-remove-overlays org-clock-report
org-clocktable-shift org-dblock-write:clocktable
- org-get-clocktable)))
+ org-get-clocktable org-resolve-clocks)))
(defun org-clock-update-time-maybe ()
"If this is a CLOCK line, update it and return t.
--
1.6.5
[-- Attachment #3: Type: text/plain, Size: 204 bytes --]
_______________________________________________
Emacs-orgmode mailing list
Remember: use `Reply All' to send replies to the list.
Emacs-orgmode@gnu.org
http://lists.gnu.org/mailman/listinfo/emacs-orgmode
^ permalink raw reply related [flat|nested] 16+ messages in thread
* Re: Patch for resolving "away time" when clocked in
2009-10-16 16:45 ` John Wiegley
@ 2009-10-16 18:09 ` Gregory J. Grubbs
2009-10-16 18:43 ` John Wiegley
0 siblings, 1 reply; 16+ messages in thread
From: Gregory J. Grubbs @ 2009-10-16 18:09 UTC (permalink / raw)
To: emacs-orgmode
John Wiegley <jwiegley@gmail.com> writes:
> On Oct 16, 2009, at 11:59 AM, Gregory J. Grubbs wrote:
>
>> One suggestion: I think when your code stops on a clock and prompts to
>> keep/subtract/cancel, it should expand the drawer (for those of us who
>> use drawers).
>
> I've added this to the version of the patch below.
>
> As another question:
>
> When attempting to clock into A, and org-resolve-clocks finds a
> dangling clock in B, and the user presses "k" or "s" (i.e., not K or
> S), do you expect it to clock you into A or into B when the resolution
> is done? Right now K will continue the clock-in to A, but k will
> abort the clock-in to A and clock you into B to resume that task.
Since my intention was to clock in to A, I would find it confusing in
any event to suddenly find myself clocked into B. Even in the case of a
dangling clock caused by an emacs crash, I would prefer to stay in
control! Should task B strike me as the task I would rather be clocking
in instead of task A, I would expect to either hit C-g and clock into B,
or go through the list of all dangling clocks and come back to B.
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: Re: Patch for resolving "away time" when clocked in
2009-10-16 18:09 ` Gregory J. Grubbs
@ 2009-10-16 18:43 ` John Wiegley
0 siblings, 0 replies; 16+ messages in thread
From: John Wiegley @ 2009-10-16 18:43 UTC (permalink / raw)
To: Gregory J. Grubbs; +Cc: emacs-orgmode
On Oct 16, 2009, at 2:09 PM, Gregory J. Grubbs wrote:
> Since my intention was to clock in to A, I would find it confusing in
> any event to suddenly find myself clocked into B. Even in the case
> of a
> dangling clock caused by an emacs crash, I would prefer to stay in
> control! Should task B strike me as the task I would rather be
> clocking
> in instead of task A, I would expect to either hit C-g and clock
> into B,
> or go through the list of all dangling clocks and come back to B.
Ok, then if the resolution logic is being triggered by a clock-in
event, I won't interrupt the user's clock in. In this case both s and
S will be equivalent.
John
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: Patch for resolving "away time" when clocked in
2009-10-16 7:03 Patch for resolving "away time" when clocked in John Wiegley
2009-10-16 14:25 ` Jeff Kowalczyk
2009-10-16 15:59 ` Patch for resolving "away time" when clocked in Gregory J. Grubbs
@ 2009-10-19 15:41 ` Bernt Hansen
2009-10-19 20:44 ` John Wiegley
2009-10-20 11:57 ` James TD Smith
3 siblings, 1 reply; 16+ messages in thread
From: Bernt Hansen @ 2009-10-19 15:41 UTC (permalink / raw)
To: John Wiegley; +Cc: Org-mode Mode
John Wiegley <jwiegley@gmail.com> writes:
> Looking for anyone who uses Org's time clocking facilities and is
> willing to test this with me. I've been using it for a couple of days
> now. The functionality is based on the way the commercial app
> OfficeTime handles idleness.
>
> Excerpt from the new manual section:
Hi John, I saw your message late last week but didn't have time to
respond. Now that I'm back from the weekend I see that it's already
added to org-mode's git repository ... and of course it wreaks havoc on
my time clocking workflow :)
I intended to help with testing your patches but unfortunately did not
have time to pursue that yet.
I normally clock tasks in, switch to another task and clock that in
without clocking out the first task. This now prompts me to keep the
minutes and how many for each time I do this - so my single I (on the
agenda) now changes to I k RET which is much less convenient for me.
Is there a way to turn off the idle time check to support the old
functionality?
For now I'm using Org-mode version 6.31trans (release_6.31.85.gc879) so
I can get through my day :)
Regards,
Bernt
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: Patch for resolving "away time" when clocked in
2009-10-16 7:03 Patch for resolving "away time" when clocked in John Wiegley
` (2 preceding siblings ...)
2009-10-19 15:41 ` Bernt Hansen
@ 2009-10-20 11:57 ` James TD Smith
2009-10-20 16:43 ` John Wiegley
3 siblings, 1 reply; 16+ messages in thread
From: James TD Smith @ 2009-10-20 11:57 UTC (permalink / raw)
To: John Wiegley; +Cc: Org-mode Mode
Hi John,
On 2009-10-16 03:03:19(-0400), John Wiegley wrote:
> Looking for anyone who uses Org's time clocking facilities and is
> willing to test this with me. I've been using it for a couple of days
> now. The functionality is based on the way the commercial app
> OfficeTime handles idleness.
I've been using org with this patch for a couple of days and I've found a
problem. I have several remember templates for handling phone calls, which clock
in automatically. org-clock-resolve fails when the remember buffer is open, I
think because the rememeber buffer somehow got into the result of
org-files-list.
I've changed org-files-list in my org install to
--8<---------------cut here---------------start------------->8---
(defun org-files-list ()
"Return `org-agenda-files' list, plus all open org-mode files.
This is useful for operations that need to scan all of a user's
open and agenda-wise Org files."
(let ((files (mapcar 'expand-file-name org-agenda-files)))
(dolist (buf (buffer-list))
(with-current-buffer buf
(if (and (eq major-mode 'org-mode) (buffer-file-name)
(not (equal (buffer-name) "*Remember*")))
(let ((file (expand-file-name (buffer-file-name))))
(unless (member file files)
(push file files))))))
files))
--8<---------------cut here---------------end--------------->8---
which seems to have fixed the problem.
--
|-<James TD Smith>-<email/ahktenzero@mohorovi.cc>-|
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: Patch for resolving "away time" when clocked in
2009-10-20 11:57 ` James TD Smith
@ 2009-10-20 16:43 ` John Wiegley
0 siblings, 0 replies; 16+ messages in thread
From: John Wiegley @ 2009-10-20 16:43 UTC (permalink / raw)
To: James TD Smith; +Cc: Org-mode Mode
On Oct 20, 2009, at 7:57 AM, James TD Smith wrote:
> I've been using org with this patch for a couple of days and I've
> found a
> problem. I have several remember templates for handling phone calls,
> which clock
> in automatically. org-clock-resolve fails when the remember buffer
> is open, I
> think because the rememeber buffer somehow got into the result of
> org-files-list.
This was fixed just recently by making sure that only real files are
returned by `org-files-list'.
John
^ permalink raw reply [flat|nested] 16+ messages in thread
end of thread, other threads:[~2009-10-20 16:43 UTC | newest]
Thread overview: 16+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2009-10-16 7:03 Patch for resolving "away time" when clocked in John Wiegley
2009-10-16 14:25 ` Jeff Kowalczyk
2009-10-16 16:32 ` John Wiegley
2009-10-16 17:02 ` Jeff Kowalczyk
2009-10-16 17:41 ` John Wiegley
2009-10-16 17:52 ` John Wiegley
2009-10-16 18:13 ` Patch for resolving Jeff Kowalczyk
2009-10-16 15:59 ` Patch for resolving "away time" when clocked in Gregory J. Grubbs
2009-10-16 16:45 ` John Wiegley
2009-10-16 18:09 ` Gregory J. Grubbs
2009-10-16 18:43 ` John Wiegley
2009-10-19 15:41 ` Bernt Hansen
2009-10-19 20:44 ` John Wiegley
2009-10-19 22:02 ` Bernt Hansen
2009-10-20 11:57 ` James TD Smith
2009-10-20 16:43 ` John Wiegley
Code repositories for project(s) associated with this public inbox
https://git.savannah.gnu.org/cgit/emacs/org-mode.git
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).