;;; org-clock.el --- The time clocking code for Org-mode
-;; Copyright (C) 2004, 2005, 2006, 2007, 2008 Free Software Foundation, Inc.
+;; Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010
+;; Free Software Foundation, Inc.
;; Author: Carsten Dominik <carsten at orgmode dot org>
;; Keywords: outlines, hypermedia, calendar, wp
;; Homepage: http://orgmode.org
-;; Version: 6.15d
+;; Version: 6.33x
;;
;; This file is part of GNU Emacs.
;;
: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
(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.
: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)
+ (const :tag "Just the history" history)
(const :tag "Clock and history" t)
(const :tag "No persistence" nil)))
(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 into this task today
+repeat All time clocked into this task since last repeat
+all All time ever recorded for this task
+auto Automatically, 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")))
+
+(defcustom org-clock-clocktable-default-properties '(:maxlevel 2 :scope file)
+ "Default properties for new clocktables."
+ :group 'org-clock
+ :type 'plist)
+
+(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")))
+
+(defcustom org-clock-auto-clock-resolution 'when-no-clock-is-running
+ "When to automatically resolve open clocks found in Org buffers."
+ :group 'org-clock
+ :type '(choice
+ (const :tag "Never" nil)
+ (const :tag "Always" t)
+ (const :tag "When no clock is running" when-no-clock-is-running)))
+
+(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.")
+(defvar org-clock-has-been-used nil
+ "Has the clock been used during the current Emacs session?")
+
;;; The clock for measuring work time.
(defvar org-mode-line-string "")
(put 'org-mode-line-string 'risky-local-variable t)
-(defvar org-mode-line-timer nil)
-(defvar org-clock-heading "")
+(defvar org-clock-mode-line-timer nil)
+(defvar org-clock-idle-timer nil)
+(defvar org-clock-heading) ; defined in org.el
(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")
+
+(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.")
(defvar org-clock-interrupted-task (make-marker)
"Marker pointing to the task that has been interrupted by the current clock.")
-(defvar org-clock-mode-map (make-sparse-keymap))
-(define-key org-clock-mode-map [mode-line mouse-2] 'org-clock-goto)
+(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."
(defun org-clock-save-markers-for-cut-and-paste (beg end)
"Save relative positions of markers in region."
(org-check-and-save-marker org-clock-marker beg end)
+ (org-check-and-save-marker org-clock-hd-marker beg end)
(org-check-and-save-marker org-clock-default-task beg end)
(org-check-and-save-marker org-clock-interrupted-task beg end)
(mapc (lambda (m) (org-check-and-save-marker m beg end))
(defun org-clock-select-task (&optional prompt)
"Select a task that recently was associated with clocking."
(interactive)
- (let (sel-list rpl file task (i 0) s)
+ (let (sel-list rpl (i 0) s)
(save-window-excursion
(org-switch-to-buffer-other-window
(get-buffer-create "*Clock Task Select*"))
(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
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-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-map
- 'mouse-face (if (featurep 'xemacs) 'highlight 'mode-line-highlight)))
- (force-mode-line-update)))
+(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 ()
+ (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 time spent on this task in
+previous clocking intervals."
+ (let ((currently-clocked-time
+ (floor (- (org-float-time)
+ (org-float-time 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 format hh:mm or mm.
+When the string 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)
+ (message "Effort is now %s" org-clock-effort))))
+
+(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-notify
+ (format "Task '%s' should be finished by now. (%s)"
+ org-clock-heading org-clock-effort) t))
+ (setq org-clock-notification-was-shown nil)))))
+
+(defun org-notify (notification &optional play-sound)
+ "Send a NOTIFICATION and maybe PLAY-SOUND."
+ (org-show-notification notification)
+ (if play-sound (org-clock-play-sound)))
+
+(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 "%s" 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)
+ (let ((file (expand-file-name org-clock-sound)))
+ (if (file-exists-p file)
+ (if (org-program-exists "aplay")
+ (start-process "org-clock-play-notification" nil
+ "aplay" file)
+ (condition-case nil
+ (play-sound-file file)
+ (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.")
+(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)))
+
+(defvar org-clock-clocking-in nil)
+(defvar org-clock-resolving-clocks nil)
+(defvar org-clock-resolving-clocks-due-to-idleness nil)
+
+(defun org-clock-resolve-clock (clock resolve-to &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"
+ (let ((org-clock-resolving-clocks t))
+ (cond
+ ((null resolve-to)
+ (org-clock-clock-cancel clock)
+ (if (and restart-p (not org-clock-clocking-in))
+ (org-clock-clock-in clock)))
+
+ ((eq resolve-to 'now)
+ (if restart-p
+ (error "RESTART-P is not valid here"))
+ (if (or close-p org-clock-clocking-in)
+ (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"))
+ (org-clock-clock-out clock fail-quietly resolve-to)
+ (unless org-clock-clocking-in
+ (if close-p
+ (setq org-clock-left-over-time 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
+ (save-window-excursion
+ (save-excursion
+ (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
+ (let ((logbook
+ (if (stringp org-clock-into-drawer)
+ (concat ":" org-clock-into-drawer ":")
+ ":LOGBOOK:")))
+ (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))))
+ (default (floor (/ (org-float-time
+ (time-subtract (current-time) last-valid)) 60)))
+ (keep (and (memq ch '(?k ?K))
+ (read-number "Keep how many minutes? " default)))
+ (subtractp (memq ch '(?s ?S)))
+ (barely-started-p (< (- (org-float-time last-valid)
+ (org-float-time (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 away, and they've ask to subtract all the
+ ;; time...
+ start-over)
+ nil)
+ (subtractp
+ last-valid)
+ ((= keep default)
+ 'now)
+ (t
+ (time-add last-valid (seconds-to-time (* 60 keep)))))
+ (memq ch '(?K ?S))
+ (and start-over
+ (not (memq ch '(?K ?S ?C))))
+ fail-quietly))))
+
+(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-files-list))
+ (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 started %d mins ago"
+ (floor
+ (/ (- (org-float-time (current-time))
+ (org-float-time (cdr clock))) 60))))))
+ (or last-valid
+ (cdr clock)))))))))))
+
+(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
+ (org-float-time 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-x11-idle-seconds ()
+ "Return the current X11 idle time in seconds"
+ (/ (string-to-number (shell-command-to-string "x11idle")) 1000))
+
+(defun org-user-idle-seconds ()
+ "Return the number of seconds the user has been idle for.
+This routine returns a floating point number."
+ (if (or (eq system-type 'darwin) (eq window-system 'x))
+ (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 (if (eq system-type 'darwin)
+ (org-mac-idle-seconds)
+ (org-x11-idle-seconds)))
+ emacs-idle))
+ (org-emacs-idle-seconds)))
+
+(defvar org-clock-user-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)
+ org-clock-marker)
+ (let ((org-clock-user-idle-seconds (org-user-idle-seconds))
+ (org-clock-user-idle-start
+ (time-subtract (current-time)
+ (seconds-to-time org-clock-user-idle-seconds)))
+ (org-clock-resolving-clocks-due-to-idleness t))
+ (if (> org-clock-user-idle-seconds (* 60 org-clock-idle-time))
+ (org-clock-resolve
+ (cons org-clock-marker
+ org-clock-start-time)
+ (function
+ (lambda (clock)
+ (format "Clocked in & idle for %.1f mins"
+ (/ (org-float-time
+ (time-subtract (current-time)
+ org-clock-user-idle-start))
+ 60.0))))
+ org-clock-user-idle-start)))))
+
(defun org-clock-in (&optional select)
"Start the clock on the current item.
If necessary, clock-out of the currently active clock.
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)
+ (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)))
+ (when (and org-clock-auto-clock-resolution
+ (or (not interrupting)
+ (eq t org-clock-auto-clock-resolution))
+ (not org-clock-clocking-in)
+ (not org-clock-resolving-clocks))
+ (setq org-clock-left-over-time nil)
+ (let ((org-clock-clocking-in t))
+ (org-resolve-clocks))) ; check if any clocks are dangling
(when (equal select '(4))
(setq selected-task (org-clock-select-task "Clock-in on task: "))
(if selected-task
(marker-position org-clock-marker)
(marker-buffer org-clock-marker))
(org-clock-out t))
-
+
(when (equal select '(16))
;; Mark as default clocking task
- (save-excursion
- (org-back-to-heading t)
- (move-marker org-clock-default-task (point))))
-
- (setq target-pos (point)) ;; we want to clock in at this location
+ (org-clock-mark-default-task))
+
+ ;; Clock in at which position?
+ (setq target-pos
+ (if (and (eobp) (not (org-on-heading-p)))
+ (point-at-bol 0)
+ (point)))
+ (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
(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!
(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-start-time (current-time))
- (setq ts (org-insert-time-stamp org-clock-start-time 'with-hm 'inactive))))
+ (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
+ (or (and left-over
+ (y-or-n-p
+ (format
+ "You stopped another clock %d mins ago; start this one from then? "
+ (/ (- (org-float-time (current-time))
+ (org-float-time 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))
+ (move-marker org-clock-hd-marker
+ (save-excursion (org-back-to-heading t) (point))
+ (buffer-base-buffer))
+ (setq org-clock-has-been-used t)
(or global-mode-string (setq global-mode-string '("")))
(or (memq 'org-mode-line-string global-mode-string)
(setq global-mode-string
(append global-mode-string '(org-mode-line-string))))
- (org-update-mode-line)
- (setq org-mode-line-timer
- (run-with-timer 60 60 'org-update-mode-line))
- (message "Clock started at %s" ts)))))))
-
-(defun org-clock-find-position ()
- "Find the location where the next clock line should be inserted."
+ (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)))))))
+
+(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
(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)
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)
;; 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)
+(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)
(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-mode-line-timer
- (cancel-timer org-mode-line-timer)
- (setq org-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 (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))
+ 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)
+ (move-marker org-clock-hd-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))
+ (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
+ (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."
(save-excursion
(set-buffer (marker-buffer org-clock-marker))
(goto-char org-clock-marker)
- (delete-region (1- (point-at-bol)) (point-at-eol)))
+ (delete-region (1- (point-at-bol)) (point-at-eol))
+ ;; Just in case, remove any empty LOGBOOK left over
+ (org-remove-empty-drawer-at "LOGBOOK" (point)))
+ (move-marker org-clock-marker nil)
+ (move-marker org-clock-hd-marker nil)
(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]*"
(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 (org-float-time tstart)))
+ (if (consp tend) (setq tend (org-float-time tend)))
(remove-text-properties (point-min) (point-max) '(:org-clock-minutes t))
(save-excursion
(goto-char (point-max))
;; Two time stamps
(setq ts (match-string 2)
te (match-string 3)
- ts (time-to-seconds
+ ts (org-float-time
(apply 'encode-time (org-parse-time-string ts)))
- te (time-to-seconds
+ te (org-float-time
(apply 'encode-time (org-parse-time-string te)))
ts (if tstart (max ts tstart) ts)
te (if tend (min te tend) te)
(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
in the echo area."
(interactive)
- (org-remove-clock-overlays)
+ (org-clock-remove-overlays)
(let (time h m p)
(org-clock-sum)
(unless total-only
(point) :org-clock-minutes)))
(goto-char p)
(when (setq time (get-text-property p :org-clock-minutes))
- (org-put-clock-overlay time (funcall outline-level))))
+ (org-clock-put-overlay time (funcall outline-level))))
(setq h (/ org-clock-file-total-minutes 60)
m (- org-clock-file-total-minutes (* 60 h)))
;; Arrange to remove the overlays upon next change.
(when org-remove-highlights-with-change
- (org-add-hook 'before-change-functions 'org-remove-clock-overlays
+ (org-add-hook 'before-change-functions 'org-clock-remove-overlays
nil 'local))))
- (message (concat "Total file time: " org-time-clocksum-format " (%d hours and %d minutes)") h m h m)))
+ (if org-time-clocksum-use-fractional
+ (message (concat "Total file time: " org-time-clocksum-fractional-format
+ " (%d hours and %d minutes)")
+ (/ (+ (* h 60.0) m) 60.0) h m)
+ (message (concat "Total file time: " org-time-clocksum-format
+ " (%d hours and %d minutes)") h m h m))))
(defvar org-clock-overlays nil)
(make-variable-buffer-local 'org-clock-overlays)
-(defun org-put-clock-overlay (time &optional level)
+(defun org-clock-put-overlay (time &optional level)
"Put an overlays on the current line, displaying TIME.
If LEVEL is given, prefix time with a corresponding number of stars.
This creates a new overlay and stores it in `org-clock-overlays', so that it
will be easy to remove."
(let* ((c 60) (h (floor (/ time 60))) (m (- time (* 60 h)))
(l (if level (org-get-valid-level level 0) 0))
- (fmt (concat "%s " org-time-clocksum-format "%s"))
+ (fmt (concat "%s " (if org-time-clocksum-use-fractional
+ org-time-clocksum-fractional-format
+ org-time-clocksum-format) "%s"))
(off 0)
ov tx)
(org-move-to-column c)
(setq ov (org-make-overlay (1- (point)) (point-at-eol))
tx (concat (buffer-substring (1- (point)) (point))
(make-string (+ off (max 0 (- c (current-column)))) ?.)
- (org-add-props (format fmt
- (make-string l ?*) h m
- (make-string (- 16 l) ?\ ))
- '(face secondary-selection))
+ (org-add-props (if org-time-clocksum-use-fractional
+ (format fmt
+ (make-string l ?*)
+ (/ (+ (* h 60.0) m) 60.0)
+ (make-string (- 16 l) ?\ ))
+ (format fmt
+ (make-string l ?*) h m
+ (make-string (- 16 l) ?\ )))
+ (list 'face 'org-clock-overlay))
""))
(if (not (featurep 'xemacs))
(org-overlay-put ov 'display tx)
(org-overlay-put ov 'end-glyph (make-glyph tx)))
(push ov org-clock-overlays)))
-(defun org-remove-clock-overlays (&optional beg end noremove)
+(defun org-clock-remove-overlays (&optional beg end noremove)
"Remove the occur highlights from the buffer.
BEG and END are ignored. If NOREMOVE is nil, remove this function
from the `before-change-functions' in the current buffer."
(setq org-clock-overlays nil)
(unless noremove
(remove-hook 'before-change-functions
- 'org-remove-clock-overlays 'local))))
+ 'org-clock-remove-overlays 'local))))
(defvar state) ;; dynamically scoped into this function
(defun org-clock-out-if-current ()
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))
When called with a prefix argument, move to the first clock table in the
buffer and update it."
(interactive "P")
- (org-remove-clock-overlays)
+ (org-clock-remove-overlays)
(when arg
(org-find-dblock "clocktable")
(org-show-entry))
(if (org-in-clocktable-p)
(goto-char (org-in-clocktable-p))
- (org-create-dblock (list :name "clocktable"
- :maxlevel 2 :scope 'file)))
+ (org-create-dblock (append (list :name "clocktable")
+ org-clock-clocktable-default-properties)))
(org-update-dblock))
(defun org-in-clocktable-p ()
(setq date (calendar-gregorian-from-absolute
(calendar-absolute-from-iso (list w 1 y))))
(setq d (nth 1 date) month (car date) y (nth 2 date)
+ dow 1
key 'week))
((string-match "^\\([0-9]+\\)-\\([0-9]\\{1,2\\}\\)-\\([0-9]\\{1,2\\}\\)$" skey)
(setq y (string-to-number (match-string 1 skey))
((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
(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
(when (and te (listp te))
(setq te (format "%4d-%02d-%02d" (nth 2 te) (car te) (nth 1 te))))
;; Now the times are strings we can parse.
- (if ts (setq ts (time-to-seconds
+ (if ts (setq ts (org-float-time
(apply 'encode-time (org-parse-time-string ts)))))
- (if te (setq te (time-to-seconds
+ (if te (setq te (org-float-time
(apply 'encode-time (org-parse-time-string te)))))
(move-marker ins (point))
(setq ipos (point))
(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))))))
(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")
(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))))
(when block
(setq cc (org-clock-special-range block nil t)
ts (car cc) te (nth 1 cc) range-text (nth 2 cc)))
- (if ts (setq ts (time-to-seconds
+ (if ts (setq ts (org-float-time
(apply 'encode-time (org-parse-time-string ts)))))
- (if te (setq te (time-to-seconds
+ (if te (setq te (org-float-time
(apply 'encode-time (org-parse-time-string te)))))
(setq p1 (plist-put p1 :header ""))
(setq p1 (plist-put p1 :step nil))
(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")
tot))))
0))))
+(defvar org-clock-loaded nil
+ "Was the clock file loaded?")
+
(defun org-clock-save ()
"Persist various clock-related data to disk.
The details of what will be saved are regulated by the variable
`org-clock-persist'."
- (when org-clock-persist
+ (when (and org-clock-persist
+ (or org-clock-loaded
+ org-clock-has-been-used
+ (not (file-exists-p org-clock-persist-file))))
(let (b)
(with-current-buffer (find-file (expand-file-name org-clock-persist-file))
(progn
(insert (format ";; org-persist.el - %s at %s\n"
system-name (format-time-string
(cdr org-time-stamp-formats))))
- (if (and (setq b (marker-buffer org-clock-marker))
+ (if (and (memq org-clock-persist '(t clock))
+ (setq b (marker-buffer org-clock-marker))
(setq b (or (buffer-base-buffer b) b))
(buffer-live-p b)
(buffer-file-name b)
"))\n"))
;; Store clocked task history. Tasks are stored reversed to make
;; reading simpler
- (when (and org-clock-history (eq org-clock-persist t))
+ (when (and (memq org-clock-persist '(t history))
+ org-clock-history)
(insert
"(setq stored-clock-history '("
(mapconcat
(save-buffer)
(kill-buffer (current-buffer)))))))
-(defvar org-clock-loaded nil
- "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)
(when (file-exists-p (car resume-clock))
(with-current-buffer (find-file (car resume-clock))
(goto-char (cdr resume-clock))
- (org-clock-in)
- (if (org-invisible-p)
- (org-show-context)))))))))
+ (let ((org-clock-auto-clock-resolution nil))
+ (org-clock-in)
+ (if (org-invisible-p)
+ (org-show-context))))))))))
;;;###autoload
(defun org-clock-persistence-insinuate ()
(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