diff --git a/lisp/org-clock.el b/lisp/org-clock.el index 87ca180..2890155 100644 --- a/lisp/org-clock.el +++ b/lisp/org-clock.el @@ -148,6 +148,15 @@ All this depends on running `org-clock-persistence-insinuate' in .emacs" :group 'org-clock :type 'boolean) + +(defcustom org-clock-sound nil + "Sound that will play once timer is expired. +If you don't have alsa, it is better to be .wav file" + :group 'org-clock + :type 'string + ) + + ;;; The clock for measuring work time. (defvar org-mode-line-string "") @@ -158,6 +167,9 @@ All this depends on running `org-clock-persistence-insinuate' in .emacs" (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-history nil "List of marker pointing to recent clocked tasks.") @@ -254,9 +266,9 @@ pointing to it." (org-get-category))) heading (org-get-heading 'notags) prefix (save-excursion - (org-back-to-heading t) - (looking-at "\\*+ ") - (match-string 0)) + (org-back-to-heading t) + (looking-at "\\*+ ") + (match-string 0)) task (substring (org-fontify-like-in-org-mode (concat prefix heading) @@ -266,24 +278,100 @@ pointing to it." (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 effort estimate was defined for current item, then use 01:30/01:50 format (clocked/estimated). +If not, then 01:50 format (clocked). +" + (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. 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))) + (if org-clock-effort (org-clock-notify-once-if-expired)) + (force-mode-line-update)) + +(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 then we estimated before. +Notification is shown only once." + (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) + (if (not org-clock-notification-was-shown) + (progn (org-clock-play-sound) + (show-notification (format "Task '%s' should be finished by now. (%s)" + org-clock-heading org-clock-effort)) + (setq org-clock-notification-was-shown t))) + (setq org-clock-notification-was-shown nil) + ) + )) + +(setq org-clock-sound "/usr/share/sounds/purple/login.wav") + +(defun show-notification (notification) + "Show notification. Use libnotify, if available." + (if (program-exists "notify-send") + (start-process "emacs-timer-notification" nil "notify-send" notification) + (message notification) + )) + + +(defun org-clock-play-sound () + "Play sound. +Use alsa's aplay tool if available." + (if (not (eq nil org-clock-sound)) + (if (program-exists "aplay") + (start-process "org-clock-play-notification" nil "aplay" org-clock-sound) + (play-sound-file org-clock-sound)) + (progn (beep t) (beep t))) + ) + +(defun 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)) + )) + + +(defun org-clock-get-clocked-time () + "In minutes." + (let ((currently-clocked-time (floor (- (time-to-seconds (current-time)) + (time-to-seconds org-clock-start-time)) 60))) + ;; (if org-clock-show-total-time + ;; ;; TODO: make total-clocked-time TOTAL, and not current clocked time :) + ;; currently-clocked-time + currently-clocked-time + ;; ) + )) + + (defvar org-clock-mode-line-entry nil "Information for the modeline about the running clock.") @@ -387,6 +475,7 @@ the clocking selection, associated with the letter `d'." (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-start-time (current-time)) (setq ts (org-insert-time-stamp org-clock-start-time 'with-hm 'inactive)))) (move-marker org-clock-marker (point) (buffer-base-buffer))