diff options
Diffstat (limited to 'lisp/org/org-clock.el')
-rw-r--r-- | lisp/org/org-clock.el | 606 |
1 files changed, 481 insertions, 125 deletions
diff --git a/lisp/org/org-clock.el b/lisp/org/org-clock.el index 0a0f8d0292a..4b96dae101b 100644 --- a/lisp/org/org-clock.el +++ b/lisp/org/org-clock.el @@ -6,7 +6,7 @@ ;; Author: Carsten Dominik <carsten at orgmode dot org> ;; Keywords: outlines, hypermedia, calendar, wp ;; Homepage: http://orgmode.org -;; Version: 6.21b +;; Version: 6.29c ;; ;; This file is part of GNU Emacs. ;; @@ -41,22 +41,28 @@ :tag "Org Clock" :group 'org-progress) -(defcustom org-clock-into-drawer 2 +(defcustom org-clock-into-drawer org-log-into-drawer "Should clocking info be wrapped into a drawer? -When t, clocking info will always be inserted into a :CLOCK: drawer. +When t, clocking info will always be inserted into a :LOGBOOK: drawer. If necessary, the drawer will be created. When nil, the drawer will not be created, but used when present. When an integer and the number of clocking entries in an item -reaches or exceeds this number, a drawer will be created." +reaches or exceeds this number, a drawer will be created. +When a string, it names the drawer to be used. + +The default for this variable is the value of `org-log-into-drawer', +which see." :group 'org-todo :group 'org-clock :type '(choice (const :tag "Always" t) (const :tag "Only when drawer exists" nil) - (integer :tag "When at least N clock entries"))) + (integer :tag "When at least N clock entries") + (const :tag "Into LOGBOOK drawer" "LOGBOOK") + (string :tag "Into Drawer named..."))) (defcustom org-clock-out-when-done t - "When non-nil, the clock will be stopped when the relevant entry is marked DONE. + "When non-nil, clock will be stopped when the clocked entry is marked DONE. A nil value means, clock will keep running until stopped explicitly with `C-c C-x C-o', or until the clock is started in a different item." :group 'org-clock @@ -80,11 +86,29 @@ state to switch it to." (string :tag "State") (symbol :tag "Function"))) +(defcustom org-clock-out-switch-to-state nil + "Set task to a special todo state after clocking out. +The value should be the state to which the entry should be +switched. If the value is a function, it must take one +parameter (the current TODO state of the item) and return the +state to switch it to." + :group 'org-clock + :group 'org-todo + :type '(choice + (const :tag "Don't force a state" nil) + (string :tag "State") + (symbol :tag "Function"))) + (defcustom org-clock-history-length 5 "Number of clock tasks to remember in history." :group 'org-clock :type 'integer) +(defcustom org-clock-goto-may-find-recent-task t + "Non-nil means, `org-clock-goto' can go to recent task if no active clock." + :group 'org-clock + :type 'boolean) + (defcustom org-clock-heading-function nil "When non-nil, should be a function to create `org-clock-heading'. This is the string shown in the mode line when a clock is running. @@ -93,26 +117,28 @@ The function is called with point at the beginning of the headline." :type 'function) (defcustom org-clock-string-limit 0 - "Maximum length of clock strings in the modeline. 0 means no limit" + "Maximum length of clock strings in the modeline. 0 means no limit." :group 'org-clock :type 'integer) (defcustom org-clock-in-resume nil - "If non-nil, when clocking into a task with a clock entry which -has not been closed, resume the clock from that point" + "If non-nil, resume clock when clocking into task with open clock. +When clocking into a task with a clock entry which has not been closed, +the clock can be resumed from that point." :group 'org-clock :type 'boolean) (defcustom org-clock-persist nil - "When non-nil, save the running clock when emacs is closed, and - resume it next time emacs is started. + "When non-nil, save the running clock when emacs is closed. +The clock is resumed when emacs restarts. When this is t, both the running clock, and the entire clock history are saved. When this is the symbol `clock', only the running clock is saved. When Emacs restarts with saved clock information, the file containing the running clock as well as all files mentioned in the clock history will -be visited." +be visited. +All this depends on running `org-clock-persistence-insinuate' in .emacs" :group 'org-clock :type '(choice (const :tag "Just the running clock" clock) @@ -121,21 +147,75 @@ be visited." (defcustom org-clock-persist-file (convert-standard-filename "~/.emacs.d/org-clock-save.el") - "File to save clock data to" + "File to save clock data to." :group 'org-clock :type 'string) (defcustom org-clock-persist-query-save nil - "When non-nil, ask before saving the current clock on exit" + "When non-nil, ask before saving the current clock on exit." :group 'org-clock :type 'boolean) (defcustom org-clock-persist-query-resume t - "When non-nil, ask before resuming any stored clock during -load." + "When non-nil, ask before resuming any stored clock during load." :group 'org-clock :type 'boolean) +(defcustom org-clock-sound nil + "Sound that will used for notifications. +Possible values: + +nil no sound played. +t standard Emacs beep +file name play this sound file. If not possible, fall back to beep" + :group 'org-clock + :type '(choice + (const :tag "No sound" nil) + (const :tag "Standard beep" t) + (file :tag "Play sound file"))) + +(defcustom org-clock-modeline-total 'auto + "Default setting for the time included for the modeline clock. +This can be overruled locally using the CLOCK_MODELINE_TOTAL property. +Allowed values are: + +current Only the time in the current instance of the clock +today All time clocked inot this task today +repeat All time clocked into this task since last repeat +all All time ever recorded for this task +auto Automtically, either `all', or `repeat' for repeating tasks" + :group 'org-clock + :type '(choice + (const :tag "Current clock" current) + (const :tag "Today's task time" today) + (const :tag "Since last repeat" repeat) + (const :tag "All task time" all) + (const :tag "Automatically, `all' or since `repeat'" auto))) + +(defcustom org-show-notification-handler nil + "Function or program to send notification with. +The function or program will be called with the notification +string as argument." + :group 'org-clock + :type '(choice + (string :tag "Program") + (function :tag "Function"))) + +(defvar org-clock-in-prepare-hook nil + "Hook run when preparing the clock. +This hook is run before anything happens to the task that +you want to clock in. For example, you can use this hook +to add an effort property.") +(defvar org-clock-in-hook nil + "Hook run when starting the clock.") +(defvar org-clock-out-hook nil + "Hook run when stopping the current clock.") + +(defvar org-clock-cancel-hook nil + "Hook run when cancelling the current clock.") +(defvar org-clock-goto-hook nil + "Hook run when selecting the currently clocked-in entry.") + ;;; The clock for measuring work time. (defvar org-mode-line-string "") @@ -146,6 +226,13 @@ load." (defvar org-clock-heading-for-remember "") (defvar org-clock-start-time "") +(defvar org-clock-effort "" + "Effort estimate of the currently clocking task") + +(defvar org-clock-total-time nil + "Holds total time, spent previously on currently clocked item. +This does not include the time in the currently running clock.") + (defvar org-clock-history nil "List of marker pointing to recent clocked tasks.") @@ -159,6 +246,16 @@ of a different task.") (defvar org-clock-mode-line-map (make-sparse-keymap)) (define-key org-clock-mode-line-map [mode-line mouse-2] 'org-clock-goto) +(define-key org-clock-mode-line-map [mode-line mouse-1] 'org-clock-menu) + +(defun org-clock-menu () + (interactive) + (popup-menu + '("Clock" + ["Clock out" org-clock-out t] + ["Change effort estimate" org-clock-modify-effort-estimate t] + ["Go to clock entry" org-clock-goto t] + ["Switch task" (lambda () (interactive) (org-clock-in '(4))) :active t :keys "C-u C-c C-x C-i"]))) (defun org-clock-history-push (&optional pos buffer) "Push a marker to the clock history." @@ -226,8 +323,11 @@ of a different task.") (t (error "Invalid task choice %c" rpl)))))) (defun org-clock-insert-selection-line (i marker) + "Insert a line for the clock selection menu. +And return a cons cell with the selection character integer and the marker +pointing to it." (when (marker-buffer marker) - (let (file cat task) + (let (file cat task heading prefix) (with-current-buffer (org-base-buffer (marker-buffer marker)) (save-excursion (save-restriction @@ -237,29 +337,148 @@ of a different task.") cat (or (org-get-category) (progn (org-refresh-category-properties) (org-get-category))) - task (org-get-heading 'notags))))) + heading (org-get-heading 'notags) + prefix (save-excursion + (org-back-to-heading t) + (looking-at "\\*+ ") + (match-string 0)) + task (substring + (org-fontify-like-in-org-mode + (concat prefix heading) + org-odd-levels-only) + (length prefix)))))) (when (and cat task) (insert (format "[%c] %-15s %s\n" i cat task)) (cons i marker))))) +(defun org-clock-get-clock-string () + "Form a clock-string, that will be show in the mode line. +If an effort estimate was defined for current item, use +01:30/01:50 format (clocked/estimated). +If not, show simply the clocked time like 01:50." + (let* ((clocked-time (org-clock-get-clocked-time)) + (h (floor clocked-time 60)) + (m (- clocked-time (* 60 h)))) + (if (and org-clock-effort) + (let* ((effort-in-minutes (org-hh:mm-string-to-minutes org-clock-effort)) + (effort-h (floor effort-in-minutes 60)) + (effort-m (- effort-in-minutes (* effort-h 60)))) + (format (concat "-[" org-time-clocksum-format "/" org-time-clocksum-format " (%s)]") + h m effort-h effort-m org-clock-heading)) + (format (concat "-[" org-time-clocksum-format " (%s)]") + h m org-clock-heading)))) + (defun org-clock-update-mode-line () - (let* ((delta (- (time-to-seconds (current-time)) - (time-to-seconds org-clock-start-time))) - (h (floor delta 3600)) - (m (floor (- delta (* 3600 h)) 60))) - (setq org-mode-line-string - (org-propertize - (let ((clock-string (format (concat "-[" org-time-clocksum-format " (%s)]") - h m org-clock-heading)) - (help-text "Org-mode clock is running. Mouse-2 to go there.")) - (if (and (> org-clock-string-limit 0) - (> (length clock-string) org-clock-string-limit)) - (org-propertize (substring clock-string 0 org-clock-string-limit) - 'help-echo (concat help-text ": " org-clock-heading)) - (org-propertize clock-string 'help-echo help-text))) - 'local-map org-clock-mode-line-map - 'mouse-face (if (featurep 'xemacs) 'highlight 'mode-line-highlight))) - (force-mode-line-update))) + (setq org-mode-line-string + (org-propertize + (let ((clock-string (org-clock-get-clock-string)) + (help-text "Org-mode clock is running.\nmouse-1 shows a menu\nmouse-2 will jump to task")) + (if (and (> org-clock-string-limit 0) + (> (length clock-string) org-clock-string-limit)) + (org-propertize (substring clock-string 0 org-clock-string-limit) + 'help-echo (concat help-text ": " org-clock-heading)) + (org-propertize clock-string 'help-echo help-text))) + 'local-map org-clock-mode-line-map + 'mouse-face (if (featurep 'xemacs) 'highlight 'mode-line-highlight) + 'face 'org-mode-line-clock)) + (if org-clock-effort (org-clock-notify-once-if-expired)) + (force-mode-line-update)) + +(defun org-clock-get-clocked-time () + "Get the clocked time for the current item in minutes. +The time returned includes the the time spent on this task in +previous clocking intervals." + (let ((currently-clocked-time + (floor (- (time-to-seconds (current-time)) + (time-to-seconds org-clock-start-time)) 60))) + (+ currently-clocked-time (or org-clock-total-time 0)))) + +(defun org-clock-modify-effort-estimate (&optional value) + "Add to or set the effort estimate of the item currently being clocked. +VALUE can be a number of minutes, or a string with forat hh:mm or mm. +WHen the strig starts with a + or a - sign, the current value of the effort +property will be changed by that amount. +This will update the \"Effort\" property of currently clocked item, and +the mode line." + (interactive) + (when (org-clock-is-active) + (let ((current org-clock-effort) sign) + (unless value + ;; Prompt user for a value or a change + (setq value + (read-string + (format "Set effort (hh:mm or mm%s): " + (if current + (format ", prefix + to add to %s" org-clock-effort) + ""))))) + (when (stringp value) + ;; A string. See if it is a delta + (setq sign (string-to-char value)) + (if (member sign '(?- ?+)) + (setq current (org-hh:mm-string-to-minutes (substring current 1))) + (setq current 0)) + (setq value (org-hh:mm-string-to-minutes value)) + (if (equal ?- sign) + (setq value (- current value)) + (if (equal ?+ sign) (setq value (+ current value))))) + (setq value (max 0 value) + org-clock-effort (org-minutes-to-hh:mm-string value)) + (org-entry-put org-clock-marker "Effort" org-clock-effort) + (org-clock-update-mode-line)))) + +(defvar org-clock-notification-was-shown nil + "Shows if we have shown notification already.") + +(defun org-clock-notify-once-if-expired () + "Show notification if we spent more time than we estimated before. +Notification is shown only once." + (when (marker-buffer org-clock-marker) + (let ((effort-in-minutes (org-hh:mm-string-to-minutes org-clock-effort)) + (clocked-time (org-clock-get-clocked-time))) + (if (>= clocked-time effort-in-minutes) + (unless org-clock-notification-was-shown + (setq org-clock-notification-was-shown t) + (org-clock-play-sound) + (org-show-notification + (format "Task '%s' should be finished by now. (%s)" + org-clock-heading org-clock-effort))) + (setq org-clock-notification-was-shown nil))))) + +(defun org-show-notification (notification) + "Show notification. +Use `org-show-notification-handler' if defined, +use libnotify if available, or fall back on a message." + (cond ((functionp org-show-notification-handler) + (funcall org-show-notification-handler notification)) + ((stringp org-show-notification-handler) + (start-process "emacs-timer-notification" nil + org-show-notification-handler notification)) + ((org-program-exists "notify-send") + (start-process "emacs-timer-notification" nil + "notify-send" notification)) + ;; Maybe the handler will send a message, so only use message as + ;; a fall back option + (t (message notification)))) + +(defun org-clock-play-sound () + "Play sound as configured by `org-clock-sound'. +Use alsa's aplay tool if available." + (cond + ((not org-clock-sound)) + ((eq org-clock-sound t) (beep t) (beep t)) + ((stringp org-clock-sound) + (if (file-exists-p org-clock-sound) + (if (org-program-exists "aplay") + (start-process "org-clock-play-notification" nil + "aplay" org-clock-sound) + (condition-case nil + (play-sound-file org-clock-sound) + (error (beep t) (beep t)))))))) + +(defun org-program-exists (program-name) + "Checks whenever we can locate program and launch it." + (if (eq system-type 'gnu/linux) + (= 0 (call-process "which" nil nil nil program-name)))) (defvar org-clock-mode-line-entry nil "Information for the modeline about the running clock.") @@ -272,9 +491,10 @@ clock into. When SELECT is `C-u C-u', clock into the current task and mark is as the default task, a special task that will always be offered in 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) + ts selected-task target-pos (msg-extra "")) (when (equal select '(4)) (setq selected-task (org-clock-select-task "Clock-in on task: ")) (if selected-task @@ -290,11 +510,10 @@ the clocking selection, associated with the letter `d'." (when (equal select '(16)) ;; Mark as default clocking task - (save-excursion - (org-back-to-heading t) - (move-marker org-clock-default-task (point)))) + (org-clock-mark-default-task)) (setq target-pos (point)) ;; we want to clock in at this location + (run-hooks 'org-clock-in-prepare-hook) (save-excursion (when (and selected-task (marker-buffer selected-task)) ;; There is a selected task, move to the correct buffer @@ -333,19 +552,22 @@ the clocking selection, associated with the letter `d'." (t "???"))) (setq org-clock-heading (org-propertize org-clock-heading 'face nil)) - (org-clock-find-position) + (org-clock-find-position org-clock-in-resume) (cond ((and org-clock-in-resume (looking-at - (concat "^[ \\t]* " org-clock-string + (concat "^[ \t]* " org-clock-string " \\[\\([0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\}" - " +\\sw+ +[012][0-9]:[0-5][0-9]\\)\\][ \t]*$"))) + " +\\sw+\.? +[012][0-9]:[0-5][0-9]\\)\\][ \t]*$"))) (message "Matched %s" (match-string 1)) (setq ts (concat "[" (match-string 1) "]")) (goto-char (match-end 1)) (setq org-clock-start-time (apply 'encode-time - (org-parse-time-string (match-string 1))))) + (org-parse-time-string (match-string 1)))) + (setq org-clock-effort (org-get-effort)) + (setq org-clock-total-time (org-clock-sum-current-item + (org-clock-get-sum-start)))) ((eq org-clock-in-resume 'auto-restart) ;; called from org-clock-load during startup, ;; do not interrupt, but warn! @@ -354,11 +576,21 @@ the clocking selection, associated with the letter `d'." (sit-for 2) (throw 'abort nil)) (t - (insert "\n") (backward-char 1) + (insert-before-markers "\n") + (backward-char 1) (org-indent-line-function) + (when (and (save-excursion + (end-of-line 0) + (org-in-item-p))) + (beginning-of-line 1) + (org-indent-line-to (- (org-get-indentation) 2))) (insert org-clock-string " ") + (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 ts (org-insert-time-stamp org-clock-start-time 'with-hm 'inactive)))) + (setq ts (org-insert-time-stamp org-clock-start-time + 'with-hm 'inactive)))) (move-marker org-clock-marker (point) (buffer-base-buffer)) (or global-mode-string (setq global-mode-string '(""))) (or (memq 'org-mode-line-string global-mode-string) @@ -367,10 +599,56 @@ the clocking selection, associated with the letter `d'." (org-clock-update-mode-line) (setq org-clock-mode-line-timer (run-with-timer 60 60 'org-clock-update-mode-line)) - (message "Clock started at %s" ts))))))) + (message "Clock starts at %s - %s" ts msg-extra) + (run-hooks 'org-clock-in-hook))))))) -(defun org-clock-find-position () - "Find the location where the next clock line should be inserted." +(defun org-clock-mark-default-task () + "Mark current task as default task." + (interactive) + (save-excursion + (org-back-to-heading t) + (move-marker org-clock-default-task (point)))) + +(defvar msg-extra) +(defun org-clock-get-sum-start () + "Return the time from which clock times should be counted. +This is for the currently running clock as it is displayed +in the mode line. This function looks at the properties +LAST_REPEAT and in particular CLOCK_MODELINE_TOTAL and the +corresponding variable `org-clock-modeline-total' and then +decides which time to use." + (let ((cmt (or (org-entry-get nil "CLOCK_MODELINE_TOTAL") + (symbol-name org-clock-modeline-total))) + (lr (org-entry-get nil "LAST_REPEAT"))) + (cond + ((equal cmt "current") + (setq msg-extra "showing time in current clock instance") + (current-time)) + ((equal cmt "today") + (setq msg-extra "showing today's task time.") + (let* ((dt (decode-time (current-time)))) + (setq dt (append (list 0 0 0) (nthcdr 3 dt))) + (if org-extend-today-until + (setf (nth 2 dt) org-extend-today-until)) + (apply 'encode-time dt))) + ((or (equal cmt "all") + (and (or (not cmt) (equal cmt "auto")) + (not lr))) + (setq msg-extra "showing entire task time.") + nil) + ((or (equal cmt "repeat") + (and (or (not cmt) (equal cmt "auto")) + lr)) + (setq msg-extra "showing task time since last repeat.") + (if (not lr) + nil + (org-time-string-to-time lr))) + (t nil)))) + +(defun org-clock-find-position (find-unclosed) + "Find the location where the next clock line should be inserted. +When FIND-UNCLOSED is non-nil, first check if there is an unclosed clock +line and position cursor in that line." (org-back-to-heading t) (catch 'exit (let ((beg (save-excursion @@ -380,12 +658,25 @@ the clocking selection, associated with the letter `d'." (end (progn (outline-next-heading) (point))) (re (concat "^[ \t]*" org-clock-string)) (cnt 0) - first last) + (drawer (if (stringp org-clock-into-drawer) + org-clock-into-drawer "LOGBOOK")) + first last ind-last) (goto-char beg) + (when (and find-unclosed + (re-search-forward + (concat "^[ \t]* " org-clock-string + " \\[\\([0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\}" + " +\\sw+ +[012][0-9]:[0-5][0-9]\\)\\][ \t]*$") + end t)) + (beginning-of-line 1) + (throw 'exit t)) (when (eobp) (newline) (setq end (max (point) end))) - (when (re-search-forward "^[ \t]*:CLOCK:" end t) + (when (re-search-forward (concat "^[ \t]*:" drawer ":") end t) ;; we seem to have a CLOCK drawer, so go there. (beginning-of-line 2) + (or org-log-states-order-reversed + (and (re-search-forward org-property-end-re nil t) + (goto-char (match-beginning 0)))) (throw 'exit t)) ;; Lets count the CLOCK lines (goto-char beg) @@ -394,20 +685,27 @@ the clocking selection, associated with the letter `d'." last (match-beginning 0) cnt (1+ cnt))) (when (and (integerp org-clock-into-drawer) + last (>= (1+ cnt) org-clock-into-drawer)) ;; Wrap current entries into a new drawer (goto-char last) + (setq ind-last (org-get-indentation)) (beginning-of-line 2) - (if (org-at-item-p) (org-end-of-item)) + (if (and (>= (org-get-indentation) ind-last) + (org-at-item-p)) + (org-end-of-item)) (insert ":END:\n") (beginning-of-line 0) - (org-indent-line-function) + (org-indent-line-to ind-last) (goto-char first) - (insert ":CLOCK:\n") + (insert ":" drawer ":\n") (beginning-of-line 0) (org-indent-line-function) (org-flag-drawer t) (beginning-of-line 2) + (or org-log-states-order-reversed + (and (re-search-forward org-property-end-re nil t) + (goto-char (match-beginning 0)))) (throw 'exit nil)) (goto-char beg) @@ -416,62 +714,84 @@ the clocking selection, associated with the letter `d'." ;; Planning info, skip to after it (beginning-of-line 2) (or (bolp) (newline))) - (when (eq t org-clock-into-drawer) - (insert ":CLOCK:\n:END:\n") - (beginning-of-line 0) + (when (or (eq org-clock-into-drawer t) + (stringp org-clock-into-drawer) + (and (integerp org-clock-into-drawer) + (< org-clock-into-drawer 2))) + (insert ":" drawer ":\n:END:\n") + (beginning-of-line -1) (org-indent-line-function) - (beginning-of-line 0) (org-flag-drawer t) + (beginning-of-line 2) (org-indent-line-function) - (beginning-of-line 2))))) + (beginning-of-line) + (or org-log-states-order-reversed + (and (re-search-forward org-property-end-re nil t) + (goto-char (match-beginning 0)))))))) (defun org-clock-out (&optional fail-quietly) "Stop the currently running clock. If there is no running clock, throw an error, unless FAIL-QUIETLY is set." (interactive) (catch 'exit - (if (not (marker-buffer org-clock-marker)) - (if fail-quietly (throw 'exit t) (error "No active clock"))) - (let (ts te s h m remove) - (save-excursion - (set-buffer (marker-buffer org-clock-marker)) - (save-restriction - (widen) - (goto-char org-clock-marker) - (beginning-of-line 1) - (if (and (looking-at (concat "[ \t]*" org-keyword-time-regexp)) - (equal (match-string 1) org-clock-string)) - (setq ts (match-string 2)) - (if fail-quietly (throw 'exit nil) (error "Clock start time is gone"))) - (goto-char (match-end 0)) - (delete-region (point) (point-at-eol)) - (insert "--") - (setq te (org-insert-time-stamp (current-time) 'with-hm 'inactive)) - (setq s (- (time-to-seconds (apply 'encode-time (org-parse-time-string te))) - (time-to-seconds (apply 'encode-time (org-parse-time-string ts)))) - h (floor (/ s 3600)) - s (- s (* 3600 h)) - m (floor (/ s 60)) - s (- s (* 60 s))) - (insert " => " (format "%2d:%02d" h m)) - (when (setq remove (and org-clock-out-remove-zero-time-clocks - (= (+ h m) 0))) + (if (not (marker-buffer org-clock-marker)) + (if fail-quietly (throw 'exit t) (error "No active clock"))) + (let (ts te s h m remove) + (save-excursion + (set-buffer (marker-buffer org-clock-marker)) + (save-restriction + (widen) + (goto-char org-clock-marker) (beginning-of-line 1) + (if (and (looking-at (concat "[ \t]*" org-keyword-time-regexp)) + (equal (match-string 1) org-clock-string)) + (setq ts (match-string 2)) + (if fail-quietly (throw 'exit nil) (error "Clock start time is gone"))) + (goto-char (match-end 0)) (delete-region (point) (point-at-eol)) - (and (looking-at "\n") (> (point-max) (1+ (point))) - (delete-char 1))) - (move-marker org-clock-marker nil) - (when org-log-note-clock-out - (org-add-log-setup 'clock-out nil nil nil - (concat "# Task: " (org-get-heading t) "\n\n"))) - (when org-clock-mode-line-timer - (cancel-timer org-clock-mode-line-timer) - (setq org-clock-mode-line-timer nil)) - (setq global-mode-string - (delq 'org-mode-line-string global-mode-string)) - (force-mode-line-update) - (message (concat "Clock stopped at %s after HH:MM = " org-time-clocksum-format "%s") te h m - (if remove " => LINE REMOVED" ""))))))) + (insert "--") + (setq te (org-insert-time-stamp (current-time) 'with-hm 'inactive)) + (setq s (- (time-to-seconds (apply 'encode-time (org-parse-time-string te))) + (time-to-seconds (apply 'encode-time (org-parse-time-string ts)))) + h (floor (/ s 3600)) + s (- s (* 3600 h)) + m (floor (/ s 60)) + s (- s (* 60 s))) + (insert " => " (format "%2d:%02d" h m)) + (when (setq remove (and org-clock-out-remove-zero-time-clocks + (= (+ h m) 0))) + (beginning-of-line 1) + (delete-region (point) (point-at-eol)) + (and (looking-at "\n") (> (point-max) (1+ (point))) + (delete-char 1))) + (move-marker org-clock-marker nil) + (when org-log-note-clock-out + (org-add-log-setup 'clock-out nil nil nil nil + (concat "# Task: " (org-get-heading t) "\n\n"))) + (when org-clock-mode-line-timer + (cancel-timer org-clock-mode-line-timer) + (setq org-clock-mode-line-timer nil)) + (setq global-mode-string + (delq 'org-mode-line-string global-mode-string)) + (when org-clock-out-switch-to-state + (save-excursion + (org-back-to-heading t) + (let ((org-inhibit-logging t)) + (cond + ((functionp org-clock-out-switch-to-state) + (looking-at org-complex-heading-regexp) + (let ((newstate (funcall org-clock-out-switch-to-state + (match-string 2)))) + (if newstate (org-todo newstate)))) + ((and org-clock-out-switch-to-state + (not (looking-at (concat outline-regexp "[ \t]*" + org-clock-out-switch-to-state + "\\>")))) + (org-todo org-clock-out-switch-to-state)))))) + (force-mode-line-update) + (message (concat "Clock stopped at %s after HH:MM = " org-time-clocksum-format "%s") te h m + (if remove " => LINE REMOVED" "")) + (run-hooks 'org-clock-out-hook)))))) (defun org-clock-cancel () "Cancel the running clock be removing the start timestamp." @@ -485,34 +805,44 @@ If there is no running clock, throw an error, unless FAIL-QUIETLY is set." (setq global-mode-string (delq 'org-mode-line-string global-mode-string)) (force-mode-line-update) - (message "Clock canceled")) + (message "Clock canceled") + (run-hooks 'org-clock-cancel-hook)) (defun org-clock-goto (&optional select) - "Go to the currently clocked-in entry. -With prefix arg SELECT, offer recently clocked tasks." - (interactive "P") - (let ((m (if select - (org-clock-select-task "Select task to go to: ") - org-clock-marker))) - (if (not (marker-buffer m)) - (if select - (error "No task selected") - (error "No active clock"))) + "Go to the currently clocked-in entry, or to the most recently clocked one. +With prefix arg SELECT, offer recently clocked tasks for selection." + (interactive "@P") + (let* ((recent nil) + (m (cond + (select + (or (org-clock-select-task "Select task to go to: ") + (error "No task selected"))) + ((marker-buffer org-clock-marker) org-clock-marker) + ((and org-clock-goto-may-find-recent-task + (car org-clock-history) + (marker-buffer (car org-clock-history))) + (setq recent t) + (car org-clock-history)) + (t (error "No active or recent clock task"))))) (switch-to-buffer (marker-buffer m)) (if (or (< m (point-min)) (> m (point-max))) (widen)) (goto-char m) (org-show-entry) - (org-back-to-heading) + (org-back-to-heading t) (org-cycle-hide-drawers 'children) - (recenter))) + (recenter) + (if recent + (message "No running clock, this is the most recently clocked task")) + (run-hooks 'org-clock-goto-hook))) (defvar org-clock-file-total-minutes nil "Holds the file total time in minutes, after a call to `org-clock-sum'.") - (make-variable-buffer-local 'org-clock-file-total-minutes) +(make-variable-buffer-local 'org-clock-file-total-minutes) (defun org-clock-sum (&optional tstart tend) "Sum the times for each subtree. -Puts the resulting times in minutes as a text property on each headline." +Puts the resulting times in minutes as a text property on each headline. +TSTART and TEND can mark a time range to be considered." (interactive) (let* ((bmp (buffer-modified-p)) (re (concat "^\\(\\*+\\)[ \t]\\|^[ \t]*" @@ -524,6 +854,10 @@ Puts the resulting times in minutes as a text property on each headline." (level 0) ts te dt time) + (if (stringp tstart) (setq tstart (org-time-string-to-seconds tstart))) + (if (stringp tend) (setq tend (org-time-string-to-seconds tend))) + (if (consp tstart) (setq tstart (time-to-seconds tstart))) + (if (consp tend) (setq tend (time-to-seconds tend))) (remove-text-properties (point-min) (point-max) '(:org-clock-minutes t)) (save-excursion (goto-char (point-max)) @@ -558,6 +892,14 @@ Puts the resulting times in minutes as a text property on each headline." (setq org-clock-file-total-minutes (aref ltimes 0))) (set-buffer-modified-p bmp))) +(defun org-clock-sum-current-item (&optional tstart) + "Returns time, clocked on current item in total" + (save-excursion + (save-restriction + (org-narrow-to-subtree) + (org-clock-sum tstart) + org-clock-file-total-minutes))) + (defun org-clock-display (&optional total-only) "Show subtree times in the entire buffer. If TOTAL-ONLY is non-nil, only show the total time for the entire file @@ -633,7 +975,10 @@ This is used to stop the clock after a TODO entry is marked DONE, and is only done if the variable `org-clock-out-when-done' is not nil." (when (and org-clock-out-when-done (member state org-done-keywords) - (equal (marker-buffer org-clock-marker) (current-buffer)) + (equal (or (buffer-base-buffer (marker-buffer org-clock-marker)) + (marker-buffer org-clock-marker)) + (or (buffer-base-buffer (current-buffer)) + (current-buffer))) (< (point) org-clock-marker) (> (save-excursion (outline-next-heading) (point)) org-clock-marker)) @@ -801,7 +1146,7 @@ the currently selected interval size." ((string-match "\\([0-9]+\\)\\(-\\([wW]?\\)\\([0-9]\\{1,2\\}\\)\\(-\\([0-9]\\{1,2\\}\\)\\)?\\)?" s) ;; 1 1 2 3 3 4 4 5 6 6 5 2 (setq y (string-to-number (match-string 1 s)) - wp (and (match-end 3) (match-string 3 s)) + wp (and (match-end 3) (match-string 3 s)) mw (and (match-end 4) (string-to-number (match-string 4 s))) d (and (match-end 6) (string-to-number (match-string 6 s)))) (cond @@ -842,11 +1187,12 @@ the currently selected interval size." (maxlevel (or (plist-get params :maxlevel) 3)) (step (plist-get params :step)) (emph (plist-get params :emphasize)) + (timestamp (plist-get params :timestamp)) (ts (plist-get params :tstart)) (te (plist-get params :tend)) (block (plist-get params :block)) (link (plist-get params :link)) - ipos time p level hlc hdl content recalc formula pcol + ipos time p level hlc hdl tsp props content recalc formula pcol cc beg end pos tbl tbl1 range-text rm-file-column scope-is-list st) (setq org-clock-file-total-minutes nil) (when step @@ -951,10 +1297,18 @@ the currently selected interval size." (save-match-data (org-make-org-heading-search-string (match-string 2)))) - (match-string 2)))) + (match-string 2))) + tsp (when timestamp + (setq props (org-entry-properties (point))) + (or (cdr (assoc "SCHEDULED" props)) + (cdr (assoc "TIMESTAMP" props)) + (cdr (assoc "DEADLINE" props)) + (cdr (assoc "TIMESTAMP_IA" props))))) (if (and (not multifile) (= level 1)) (push "|-" tbl)) (push (concat - "| " (int-to-string level) "|" hlc hdl hlc " |" + "| " (int-to-string level) "|" + (if timestamp (concat tsp "|") "") + hlc hdl hlc " |" (make-string (1- level) ?|) hlc (org-minutes-to-hh:mm-string time) hlc " |") tbl)))))) @@ -973,12 +1327,12 @@ the currently selected interval size." (if block (concat ", for " range-text ".") "") "\n\n")) (if scope-is-list "|File" "") - "|L|Headline|Time|\n") + "|L|" (if timestamp "Timestamp|" "") "Headline|Time|\n") (setq total-time (or total-time org-clock-file-total-minutes)) (insert-before-markers "|-\n|" (if scope-is-list "|" "") - "|" + (if timestamp "|Timestamp|" "|") "*Total time*| *" (org-minutes-to-hh:mm-string (or total-time 0)) "*|\n|-\n") @@ -1009,7 +1363,7 @@ the currently selected interval size." (t (error "invalid formula in clocktable"))) ;; Should we rescue an old formula? (when (stringp (setq content (plist-get params :content))) - (when (string-match "^\\(#\\+TBLFM:.*\\)" content) + (when (string-match "^\\([ \t]*#\\+TBLFM:.*\\)" content) (setq recalc t) (insert "\n" (match-string 1 (plist-get params :content))) (beginning-of-line 0)))) @@ -1046,10 +1400,10 @@ the currently selected interval size." (while (< ts te) (or (bolp) (insert "\n")) (setq p1 (plist-put p1 :tstart (format-time-string - (car org-time-stamp-formats) + (org-time-stamp-format nil t) (seconds-to-time ts)))) (setq p1 (plist-put p1 :tend (format-time-string - (car org-time-stamp-formats) + (org-time-stamp-format nil t) (seconds-to-time (setq ts (+ ts step)))))) (insert "\n" (if (eq step0 'day) "Daily report: " "Weekly report starting on: ") (plist-get p1 :tstart) "\n") @@ -1139,8 +1493,7 @@ The details of what will be saved are regulated by the variable "Was the clock file loaded?") (defun org-clock-load () - "Load various clock-related data from disk, optionally resuming -a stored clock" + "Load clock-related data from disk, maybe resuming a stored clock." (when (and org-clock-persist (not org-clock-loaded)) (let ((filename (expand-file-name org-clock-persist-file)) (org-clock-in-resume 'auto-restart) @@ -1186,6 +1539,9 @@ a stored clock" (add-hook 'org-mode-hook 'org-clock-load) (add-hook 'kill-emacs-hook 'org-clock-save)) +;; Suggested bindings +(org-defkey org-mode-map "\C-c\C-x\C-e" 'org-clock-modify-effort-estimate) + (provide 'org-clock) ;; arch-tag: 7b42c5d4-9b36-48be-97c0-66a869daed4c |